fix(defender-for-iot): align panel styles with the UI contracts

Defender part of 3f6259d: 14px labels and table headers, compact metric
tiles inside a segment, marker width token. Keeps the tile hairline,
shared text roles and the blue table-row marker.
This commit is contained in:
Binh 2026-10-02 14:03:06 +02:00
parent 45088e9bcd
commit bad06d15e8
3 changed files with 13 additions and 9 deletions

View File

@ -4231,6 +4231,7 @@ Verifiziert 2026-09-08 (Phase 7/13B): GPO-Quellenkorrektur fuer Work und Client
| 2026-10-06 | Phase 7: Data Hub Deviation/Passed now use the same assessed-percentage helper as Client Info; all six donuts state their population basis. 29 focused tests and synthetic Chrome Dark/Light checks passed; absolute counts, other population shares and assessment semantics retained. Client build, design and UI/API version checks passed. Nine out-of-scope report test failures, a report Local-only failure and report server type errors prevent full release verification; attempted 2.16.9 reverted to 2.16.8 pending successful production builds. | | 2026-10-06 | Phase 7: Data Hub Deviation/Passed now use the same assessed-percentage helper as Client Info; all six donuts state their population basis. 29 focused tests and synthetic Chrome Dark/Light checks passed; absolute counts, other population shares and assessment semantics retained. Client build, design and UI/API version checks passed. Nine out-of-scope report test failures, a report Local-only failure and report server type errors prevent full release verification; attempted 2.16.9 reverted to 2.16.8 pending successful production builds. |
| 2026-10-06 | Phase 7: Client Info Deviations percentage now divides by Passed + Deviations and labels Of assessed checks; zero assessed shows a dash while preserving absolute zero. Permanent UI/click invariance tests in both themes and synthetic Chrome presentation verified; unchanged counts, verdicts, scores and other donuts. Patch 2.16.8: both builds and version contracts passed; full run 1,936 passed/15 skipped with one stale API-version assertion corrected and passed in the 43-test focused rerun; Local-only/design checks passed. | | 2026-10-06 | Phase 7: Client Info Deviations percentage now divides by Passed + Deviations and labels Of assessed checks; zero assessed shows a dash while preserving absolute zero. Permanent UI/click invariance tests in both themes and synthetic Chrome presentation verified; unchanged counts, verdicts, scores and other donuts. Patch 2.16.8: both builds and version contracts passed; full run 1,936 passed/15 skipped with one stale API-version assertion corrected and passed in the 43-test focused rerun; Local-only/design checks passed. |
| 2026-10-06 | Phase 13B: GPO folder read diagnostics retain only safe error names, stages and counts, distinguish empty/denied/unreadable selections, preserve cancellation and wrap complete timeslot messages. 27 focused GPO UI tests and synthetic Chrome Dark/Light checks passed; both builds and version contracts verified for patch 2.16.7. Full run: 1,932 passed, 15 skipped, one general import timeout passed on separate rerun; Local-only/design checks passed. Customer-specific read failure remains unconfirmed; no customer path/data used, Phase 13B remains in progress. | | 2026-10-06 | Phase 13B: GPO folder read diagnostics retain only safe error names, stages and counts, distinguish empty/denied/unreadable selections, preserve cancellation and wrap complete timeslot messages. 27 focused GPO UI tests and synthetic Chrome Dark/Light checks passed; both builds and version contracts verified for patch 2.16.7. Full run: 1,932 passed, 15 skipped, one general import timeout passed on separate rerun; Local-only/design checks passed. Customer-specific read failure remains unconfirmed; no customer path/data used, Phase 13B remains in progress. |
| 2026-10-02 | Phase 7: Defender-Teil von `3f6259d` (Stile und Dashboardtest) nachgezogen. Kennzahlbeschriftungen und Tabellenkoepfe des Defender-Panels nutzen die 14px-Tabellenrolle statt der 12px-Labelrolle; die Kennzahlen stehen als kompakte Kacheln (`--tile-compact-width`) in einem Segment statt in sechs festen Spalten; der Zeilenmarker nutzt `--marker-width`. Beibehalten gegenueber dem Original: Kachelkontur `--tile-border`, gemeinsame Textrollen und die blaue Tabellenzeilen-Markierung. Nur CSS und Testassertions; nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.920 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. |
| 2026-10-02 | Phasen 6/7/13A, Produktversion 2.12.0: `fdd46e8` (Profil Microsoft Defender for IoT Alerts) angepasst uebernommen. Profil `defender-for-iot-alerts` 1.6.0 mit Domain-Modell, Source Extractor, Manifestzeilen und deklarierten Scoring-/Prioritaets-/Timeslot-Vertraegen (kein nativer Gesamtscore, native Alarm-Severity, kein Timeslot-Vergleich). Alarme werden je Alarmtyp gruppiert; unterschiedliche Beschreibungen eines Typs erscheinen als gemeinsame Woerter mit `…`, die zugehoerige Nutzerregel steht mit ausdruecklicher Governance-Freigabe in `AGENTS.md`. Werkzeuguebergreifend: Unterstriche innerhalb eines Wortes sind in `markdown.ts` keine Auszeichnung mehr; ein Profil kann per `selectExtractedTables` die Tabellen behalten, aus denen sein Extractor Findings gebildet hat; ein Statusmapping kann sein Outcome deklarieren; beide Repositories setzen eine bereits durch einen ersetzten Lauf belegte Scan-Sequenz hinter alle gespeicherten Laeufe. Die Ausblendung von `Source Observations` liegt auf dieser Basis in `FindingReader.tsx`. Zwei Korrekturen aus spaeteren Commits waren fuer Defender auf dieser Basis noetig und sind vorgezogen: aus `210bbc0` der Titel-Rueckfall in `recordTitleFor` (eine leere gewaehlte Titelspalte faellt wieder auf den Datensatztitel zurueck; mit `7d3ef5f` war dieser Rueckfall fuer alle Scanner verloren gegangen) samt Regression fuer die GPO-Zeile, und aus `21a6266` die Ausnahme in `withSourceRuleId`, die eine native ID-Spalte nicht in `Source Rule ID` umbenennt, wenn sie zugleich der Finding-Titel ist (Defender `CVE`, Alert `Title`); die Regel-ID wird dann zusaetzlich angefuegt. Nicht uebernommen: die Versionsangabe 2.10.8 des Originals. Produktversion: Minor-Erhoehung von 2.11.2 auf 2.12.0 fuer die Defender-Serie (zwei neue Scannerprofile, geraetebezogene Risikoakzeptanz); `package.json`, Root-Eintraege in `package-lock.json` und die Versionsvertragstests sind synchron. Die `securityToolId`-Zeile aus `c056c21` folgt mit dem Result-DB-Werkzeugeintrag aus `21a6266`. Neue Profile, daher kein Reimport bestehender Importe; keine SQL-Migration. Nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.920 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | | 2026-10-02 | Phasen 6/7/13A, Produktversion 2.12.0: `fdd46e8` (Profil Microsoft Defender for IoT Alerts) angepasst uebernommen. Profil `defender-for-iot-alerts` 1.6.0 mit Domain-Modell, Source Extractor, Manifestzeilen und deklarierten Scoring-/Prioritaets-/Timeslot-Vertraegen (kein nativer Gesamtscore, native Alarm-Severity, kein Timeslot-Vergleich). Alarme werden je Alarmtyp gruppiert; unterschiedliche Beschreibungen eines Typs erscheinen als gemeinsame Woerter mit `…`, die zugehoerige Nutzerregel steht mit ausdruecklicher Governance-Freigabe in `AGENTS.md`. Werkzeuguebergreifend: Unterstriche innerhalb eines Wortes sind in `markdown.ts` keine Auszeichnung mehr; ein Profil kann per `selectExtractedTables` die Tabellen behalten, aus denen sein Extractor Findings gebildet hat; ein Statusmapping kann sein Outcome deklarieren; beide Repositories setzen eine bereits durch einen ersetzten Lauf belegte Scan-Sequenz hinter alle gespeicherten Laeufe. Die Ausblendung von `Source Observations` liegt auf dieser Basis in `FindingReader.tsx`. Zwei Korrekturen aus spaeteren Commits waren fuer Defender auf dieser Basis noetig und sind vorgezogen: aus `210bbc0` der Titel-Rueckfall in `recordTitleFor` (eine leere gewaehlte Titelspalte faellt wieder auf den Datensatztitel zurueck; mit `7d3ef5f` war dieser Rueckfall fuer alle Scanner verloren gegangen) samt Regression fuer die GPO-Zeile, und aus `21a6266` die Ausnahme in `withSourceRuleId`, die eine native ID-Spalte nicht in `Source Rule ID` umbenennt, wenn sie zugleich der Finding-Titel ist (Defender `CVE`, Alert `Title`); die Regel-ID wird dann zusaetzlich angefuegt. Nicht uebernommen: die Versionsangabe 2.10.8 des Originals. Produktversion: Minor-Erhoehung von 2.11.2 auf 2.12.0 fuer die Defender-Serie (zwei neue Scannerprofile, geraetebezogene Risikoakzeptanz); `package.json`, Root-Eintraege in `package-lock.json` und die Versionsvertragstests sind synchron. Die `securityToolId`-Zeile aus `c056c21` folgt mit dem Result-DB-Werkzeugeintrag aus `21a6266`. Neue Profile, daher kein Reimport bestehender Importe; keine SQL-Migration. Nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.920 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. |
| 2026-10-02 | Phasen 7/13A: `d9ff6a8` (geraetebezogene Risikoakzeptanz fuer Defender) angepasst uebernommen. Neben der bestehenden Akzeptanz des ganzen Findings gibt es die Ziele `device-finding` (ein Device mal eine CVE) und `device-snapshot` (alle aktuellen CVEs eines Device) mit unveraenderlichem Zielabbild und append-only Historie. Migration `028_scoped_risk_acceptances.sql` legt die projektgebundene Tabelle `assessor_scoped_risk_acceptances` an (Cascade bei Projektloeschung); beide Repositories, `GET`/`POST /api/v1/projects/:id/risk-acceptances/scoped`, App-State, Defender-Panel (Abdeckungszeile, deckende Entscheidungen, `Accept all current findings`), Finding Reader/Dialog und das Accepted-Risks-Register sind erweitert. Nutzerentscheidung 2026-10-02, abweichend vom Original: (1) Die Zaehlung `current` im Register bleibt bei der Basisregel `Accepted`, `Scheduled`, `Review due`, `Review required`. (2) Geraetebezogene Entscheidungen folgen der Regel fuer ganze Findings: sie decken nur die erfassten Occurrences; nach einem Reimport oder entfernten Scan-Lauf sowie bei einer nicht erfassten Occurrence desselben Device und derselben CVE bleibt die Entscheidung mit vollstaendiger Historie erhalten, zeigt `Review required` und deckt nichts. Der Marker `capturedScopeReplaced` wird bei jedem Lesen aus den aktuellen Scan-Laeufen abgeleitet und nie gespeichert. Offen: Ersetzte geraetebezogene Entscheidungen fliessen nicht in die Reimport-Transferzaehlung je Datei und damit nicht in den Data-Hub-Verarbeitungsstatus ein; das Defender-Panel listet nur deckende Entscheidungen. Neue Regressionen: Domain (erfasste gegen ersetzte Occurrence, Marker, Widerruf, keine Persistenz des Markers), lokales Repository (Reimport desselben Berichts mit geaenderter Device-Identitaet) und Register. Der opt-in PostgreSQL-Integrationstest lief nicht; der PostgreSQL-Lesepfad des Markers ist damit nur typgeprueft. Vor der Migration wurde das verifizierte Backup `backups/assessor-pre-028-20261002-132810.dump` erstellt. Nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.908 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | | 2026-10-02 | Phasen 7/13A: `d9ff6a8` (geraetebezogene Risikoakzeptanz fuer Defender) angepasst uebernommen. Neben der bestehenden Akzeptanz des ganzen Findings gibt es die Ziele `device-finding` (ein Device mal eine CVE) und `device-snapshot` (alle aktuellen CVEs eines Device) mit unveraenderlichem Zielabbild und append-only Historie. Migration `028_scoped_risk_acceptances.sql` legt die projektgebundene Tabelle `assessor_scoped_risk_acceptances` an (Cascade bei Projektloeschung); beide Repositories, `GET`/`POST /api/v1/projects/:id/risk-acceptances/scoped`, App-State, Defender-Panel (Abdeckungszeile, deckende Entscheidungen, `Accept all current findings`), Finding Reader/Dialog und das Accepted-Risks-Register sind erweitert. Nutzerentscheidung 2026-10-02, abweichend vom Original: (1) Die Zaehlung `current` im Register bleibt bei der Basisregel `Accepted`, `Scheduled`, `Review due`, `Review required`. (2) Geraetebezogene Entscheidungen folgen der Regel fuer ganze Findings: sie decken nur die erfassten Occurrences; nach einem Reimport oder entfernten Scan-Lauf sowie bei einer nicht erfassten Occurrence desselben Device und derselben CVE bleibt die Entscheidung mit vollstaendiger Historie erhalten, zeigt `Review required` und deckt nichts. Der Marker `capturedScopeReplaced` wird bei jedem Lesen aus den aktuellen Scan-Laeufen abgeleitet und nie gespeichert. Offen: Ersetzte geraetebezogene Entscheidungen fliessen nicht in die Reimport-Transferzaehlung je Datei und damit nicht in den Data-Hub-Verarbeitungsstatus ein; das Defender-Panel listet nur deckende Entscheidungen. Neue Regressionen: Domain (erfasste gegen ersetzte Occurrence, Marker, Widerruf, keine Persistenz des Markers), lokales Repository (Reimport desselben Berichts mit geaenderter Device-Identitaet) und Register. Der opt-in PostgreSQL-Integrationstest lief nicht; der PostgreSQL-Lesepfad des Markers ist damit nur typgeprueft. Vor der Migration wurde das verifizierte Backup `backups/assessor-pre-028-20261002-132810.dump` erstellt. Nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.908 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. |
| 2026-10-02 | Phasen 6/7: Defender-Teile von `b438f25` uebernommen (nach `c7c716c`, weil der Commit zunaechst nur als NVD-Korrektur eingeordnet war). CVE-, Device- und Site-Tabelle des Defender-Panels klappen ihre Details direkt unter der angeklickten Zeile auf und wieder zu (Dreieck-Schalter, `aria-expanded`/`aria-controls`); die CVE-Tabelle fuehrt die Beschreibung nur noch im Detail. Der Extractor gibt die Tabelle `Affected Devices` als `Name`, `IP Address`, `Scanner`, `Site` aus. Abweichend vom Original nutzt die aufgeklappte Zeile nach Nutzerentscheidung die blaue Tabellenzeilen-Markierung (`--table-row-marking`, `--table-row-marking-surface`) statt Gelb, und der Drilldown bleibt auf den gemeinsamen Kachelrollen. Nicht uebernommen: die `baseSeverity`-Korrektur im NVD-Client samt Test (CVE-Anreicherung ausgelassen) und der `.gitignore`-Eintrag. Kein Profilversionssprung wie im Original; die Tabellenspalten liegen bereits unter dem danach uebernommenen Profil 1.2.0, die lokale Datenbank enthaelt keine Defender-Importe. Nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.896 bestandenen und 17 uebersprungenen Tests (ein lastbedingter Einzelausfall in `node-import-engine.test.ts` im ersten Lauf, allein und im Wiederholungslauf bestanden), Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | | 2026-10-02 | Phasen 6/7: Defender-Teile von `b438f25` uebernommen (nach `c7c716c`, weil der Commit zunaechst nur als NVD-Korrektur eingeordnet war). CVE-, Device- und Site-Tabelle des Defender-Panels klappen ihre Details direkt unter der angeklickten Zeile auf und wieder zu (Dreieck-Schalter, `aria-expanded`/`aria-controls`); die CVE-Tabelle fuehrt die Beschreibung nur noch im Detail. Der Extractor gibt die Tabelle `Affected Devices` als `Name`, `IP Address`, `Scanner`, `Site` aus. Abweichend vom Original nutzt die aufgeklappte Zeile nach Nutzerentscheidung die blaue Tabellenzeilen-Markierung (`--table-row-marking`, `--table-row-marking-surface`) statt Gelb, und der Drilldown bleibt auf den gemeinsamen Kachelrollen. Nicht uebernommen: die `baseSeverity`-Korrektur im NVD-Client samt Test (CVE-Anreicherung ausgelassen) und der `.gitignore`-Eintrag. Kein Profilversionssprung wie im Original; die Tabellenspalten liegen bereits unter dem danach uebernommenen Profil 1.2.0, die lokale Datenbank enthaelt keine Defender-Importe. Nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.896 bestandenen und 17 uebersprungenen Tests (ein lastbedingter Einzelausfall in `node-import-engine.test.ts` im ersten Lauf, allein und im Wiederholungslauf bestanden), Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. |

View File

@ -2,9 +2,9 @@
.defender-header { order: 0; } .defender-header { order: 0; }
.defender-source strong { color: var(--heading); font-weight: var(--fw-regular); } .defender-source strong { color: var(--heading); font-weight: var(--fw-regular); }
.defender-context { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2) var(--space-5); margin: 0; color: var(--muted); font-size: var(--type-meta-size); } .defender-context { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2) var(--space-5); margin: 0; color: var(--muted); font-size: var(--type-meta-size); }
.defender-metrics { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); justify-content: start; gap: var(--space-2); min-width: 0; } .defender-metrics { display: grid; grid-template-columns: repeat(auto-fill, var(--tile-compact-width)); justify-content: start; gap: var(--segment-gap); min-width: 0; padding: var(--segment-inset); border: var(--bw-hairline) solid var(--segment-border); border-radius: var(--segment-radius); background: var(--segment-surface); }
.defender-metrics article { display: grid; align-content: center; gap: var(--space-2); min-width: 0; min-height: 88px; padding: var(--tile-inset); border: var(--border-hair) solid var(--tile-border); border-radius: var(--tile-radius); background: var(--tile-surface); box-shadow: none; } .defender-metrics article { display: grid; align-content: center; gap: var(--space-2); min-width: 0; min-height: 88px; padding: var(--tile-inset); border: var(--border-hair) solid var(--tile-border); border-radius: var(--tile-radius); background: var(--tile-surface); box-shadow: none; }
.defender-metrics span { color: var(--muted); font-size: var(--type-label-size); font-weight: var(--fw-semibold); line-height: 1.3; } .defender-metrics span { color: var(--muted); font-size: var(--type-table-body-size); font-weight: var(--fw-semibold); line-height: 1.3; }
.defender-metrics strong { color: var(--heading); font-size: var(--type-h2-size); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; line-height: 1; } .defender-metrics strong { color: var(--heading); font-size: var(--type-h2-size); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; line-height: 1; }
.defender-tabs, .client-info-tabs { display: flex; flex-wrap: wrap; gap: var(--space-2); } .defender-tabs, .client-info-tabs { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.defender-tabs button, .client-info-tabs button { min-height: 36px; padding: var(--space-2) var(--space-4); border: var(--border-hair) solid transparent; border-radius: var(--btn-radius); color: var(--text); background: transparent; font-size: var(--ui-control-size); font-weight: var(--fw-semibold); cursor: pointer; } .defender-tabs button, .client-info-tabs button { min-height: 36px; padding: var(--space-2) var(--space-4); border: var(--border-hair) solid transparent; border-radius: var(--btn-radius); color: var(--text); background: transparent; font-size: var(--ui-control-size); font-weight: var(--fw-semibold); cursor: pointer; }
@ -17,7 +17,7 @@
.defender-view > header span { color: var(--muted); font-size: var(--type-meta-size); } .defender-view > header span { color: var(--muted); font-size: var(--type-meta-size); }
.defender-table-wrap { min-width: 0; overflow-x: auto; background: var(--tile-surface); } .defender-table-wrap { min-width: 0; overflow-x: auto; background: var(--tile-surface); }
.defender-table { width: 100%; border-collapse: collapse; table-layout: auto; color: var(--text); background: var(--tile-surface); font-size: var(--type-table-body-size); } .defender-table { width: 100%; border-collapse: collapse; table-layout: auto; color: var(--text); background: var(--tile-surface); font-size: var(--type-table-body-size); }
.defender-table th { padding: var(--space-2) var(--space-3); border-bottom: var(--border-hair) solid var(--line-strong); color: var(--heading); font-size: var(--type-label-size); font-weight: var(--fw-semibold); text-align: left; text-transform: uppercase; vertical-align: bottom; } .defender-table th { padding: var(--space-2) var(--space-3); border-bottom: var(--border-hair) solid var(--line-strong); color: var(--heading); font-size: var(--type-table-body-size); font-weight: var(--fw-semibold); text-align: left; text-transform: uppercase; vertical-align: bottom; }
.defender-table td { max-width: 440px; padding: var(--space-3); border-bottom: var(--border-hair) solid var(--line); line-height: 1.45; overflow-wrap: normal; word-break: normal; vertical-align: top; } .defender-table td { max-width: 440px; padding: var(--space-3); border-bottom: var(--border-hair) solid var(--line); line-height: 1.45; overflow-wrap: normal; word-break: normal; vertical-align: top; }
.defender-table td > button { padding: 0; border: 0; color: var(--heading); background: transparent; font: inherit; font-weight: var(--fw-semibold); text-align: left; cursor: pointer; } .defender-table td > button { padding: 0; border: 0; color: var(--heading); background: transparent; font: inherit; font-weight: var(--fw-semibold); text-align: left; cursor: pointer; }
.defender-table .defender-row-toggle { display: inline-flex; align-items: center; gap: var(--space-2); } .defender-table .defender-row-toggle { display: inline-flex; align-items: center; gap: var(--space-2); }
@ -25,7 +25,7 @@
.defender-table td > button:hover { background: var(--tile-surface-hover); } .defender-table td > button:hover { background: var(--tile-surface-hover); }
.defender-table td > button:focus-visible { outline: var(--bw-emphasis) solid var(--focus-ring); outline-offset: 2px; } .defender-table td > button:focus-visible { outline: var(--bw-emphasis) solid var(--focus-ring); outline-offset: 2px; }
.defender-selected-row > td { background: var(--table-row-marking-surface); } .defender-selected-row > td { background: var(--table-row-marking-surface); }
.defender-selected-row > td:first-child { box-shadow: inset 4px 0 var(--table-row-marking); } .defender-selected-row > td:first-child { box-shadow: inset var(--marker-width) 0 var(--table-row-marking); }
.defender-detail-row > td { max-width: none; padding: 0 0 var(--space-3); border-bottom-color: var(--table-row-marking); } .defender-detail-row > td { max-width: none; padding: 0 0 var(--space-3); border-bottom-color: var(--table-row-marking); }
.defender-table small { display: block; margin-top: var(--space-1); color: var(--muted); font-size: var(--type-meta-size); } .defender-table small { display: block; margin-top: var(--space-1); color: var(--muted); font-size: var(--type-meta-size); }
.defender-severity { display: inline-flex; align-items: center; gap: var(--space-2); white-space: nowrap; } .defender-severity { display: inline-flex; align-items: center; gap: var(--space-2); white-space: nowrap; }
@ -44,10 +44,8 @@
.defender-drilldown details > .defender-table, .defender-drilldown details > .defender-drilldown { margin-top: var(--space-3); } .defender-drilldown details > .defender-table, .defender-drilldown details > .defender-drilldown { margin-top: var(--space-3); }
.defender-aggregate-summary, .defender-severity-strip { display: grid; grid-template-columns: repeat(auto-fill, var(--tile-compact-width)); justify-content: start; gap: var(--segment-gap); } .defender-aggregate-summary, .defender-severity-strip { display: grid; grid-template-columns: repeat(auto-fill, var(--tile-compact-width)); justify-content: start; gap: var(--segment-gap); }
.defender-aggregate-summary > span, .defender-severity-strip article { display: grid; gap: var(--space-1); min-width: var(--tile-compact-width); padding: var(--space-3); background: var(--surface-inset); } .defender-aggregate-summary > span, .defender-severity-strip article { display: grid; gap: var(--space-1); min-width: var(--tile-compact-width); padding: var(--space-3); background: var(--surface-inset); }
.defender-aggregate-summary strong { color: var(--heading); font-size: var(--type-label-size); font-weight: var(--fw-semibold); } .defender-aggregate-summary strong { color: var(--heading); font-size: var(--type-table-body-size); font-weight: var(--fw-semibold); }
.defender-aggregate-summary span, .defender-severity-strip strong { font-size: var(--type-table-body-size); } .defender-aggregate-summary span, .defender-severity-strip strong { font-size: var(--type-table-body-size); }
.defender-severity-strip article { grid-template-columns: minmax(0, 1fr) auto; align-items: center; } .defender-severity-strip article { grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
.defender-severity-strip strong { color: var(--heading); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; } .defender-severity-strip strong { color: var(--heading); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; }
@container (max-width: 1100px) { .defender-metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); } } @container (max-width: 760px) { .defender-view > header { display: grid; } }
@container (max-width: 760px) { .defender-view > header { display: grid; } .defender-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container (max-width: 480px) { .defender-metrics { grid-template-columns: 1fr; } }

View File

@ -57,9 +57,14 @@ describe('Defender vulnerability dashboard projection', () => {
expect(dashboard).toContain('scopedRiskAcceptances={app.scopedRiskAcceptances} activeScanRunIds={app.activeScanRunIds}'); expect(dashboard).toContain('scopedRiskAcceptances={app.scopedRiskAcceptances} activeScanRunIds={app.activeScanRunIds}');
expect(dashboard).toContain('onScopedRiskAcceptance={app.backendStatus === \'connected\' ? write => app.recordScopedRiskAcceptance(write) : undefined}'); expect(dashboard).toContain('onScopedRiskAcceptance={app.backendStatus === \'connected\' ? write => app.recordScopedRiskAcceptance(write) : undefined}');
expect(styles).toContain('padding: var(--panel-inset)'); expect(styles).toContain('padding: var(--panel-inset)');
expect(styles).toContain('grid-template-columns: repeat(6, minmax(0, 1fr))'); expect(styles).toContain('grid-template-columns: repeat(auto-fill, var(--tile-compact-width))');
expect(styles).toContain('background: var(--tile-surface)'); expect(styles).toContain('background: var(--tile-surface)');
expect(styles).toMatch(/\.defender-metrics article \{[^}]*border-radius: var\(--tile-radius\)/);
expect(styles).toMatch(/\.defender-metrics span \{[^}]*font-size: var\(--type-table-body-size\)/);
expect(styles).toMatch(/\.defender-table th \{[^}]*font-size: var\(--type-table-body-size\)/);
expect(styles).toContain('box-shadow: inset var(--marker-width) 0 var(--table-row-marking)');
expect(styles).toMatch(/\.defender-drilldown \{[^}]*background: var\(--tile-surface\)/); expect(styles).toMatch(/\.defender-drilldown \{[^}]*background: var\(--tile-surface\)/);
expect(styles).toMatch(/\.defender-drilldown \{[^}]*border-radius: var\(--tile-radius\)/);
expect(styles).not.toMatch(/\.defender-drilldown \{[^}]*background: var\(--client-info-surface\)/); expect(styles).not.toMatch(/\.defender-drilldown \{[^}]*background: var\(--client-info-surface\)/);
expect(styles).toContain('outline: var(--bw-emphasis) solid var(--marking-color)'); expect(styles).toContain('outline: var(--bw-emphasis) solid var(--marking-color)');
expect(styles).toContain('outline: var(--bw-emphasis) solid var(--focus-ring)'); expect(styles).toContain('outline: var(--bw-emphasis) solid var(--focus-ring)');