Development history
The chronological development log of auditmysite — finding, fix and verification for each change, in German. The changelog lists the same changes per release.
This page keeps the full development log as it was written during development, in German: for each change the finding, the fix and how it was verified. The changelog summarizes the same changes per release.
Detailed, chronological development history for auditmysite — each entry documents a finding,
the fix, and how it was verified. Extracted from CLAUDE.md’s former “Current State” section
(plan/11-claude-md-version-drift.md) so CLAUDE.md itself stays focused on working rules and a
short current-state summary. Newest entries first (unchanged order from before the extraction).
-
Unreleased — Linkzweck aus
a11y-rules0.24.0 (Plan 98, casoon/barrierlab#13, #51):link_purposeundlink_purpose_link_onlywaren bei B5 (#743) liegen geblieben, obwohl #690 sie dorthin verschoben hatte. Sie laufen jetzt im geteilten Bestand, beide Dateien sind gelöscht, die alten Kennungen lösen überLEGACY_RULE_IDSauf.links/generic-namenutzt die bisherige lokale Liste (15 Sprachen, auch Navigationswörter wie „Start“) und die Kontextauswertung aus #569/Plan 71; mit Kontext Low statt einer eigenen Kennunglink-name-context. Neu:links/url-name,links/single-character-name,links/new-window-unannounced(prüfttarget="_blank"stattaria-haspopup, 3.2.5 AAA mit eigenem Taxonomie-Eintrag und Erklärungstext) undlinks/purpose-link-only(2.4.9). Alle sindREVIEW; generische Linktexte ohne Kontext waren lokal Verstöße. 2.4.9 und 3.2.5 zählen damit als „nur Hinweise“, die AA-Zahlen (30/25) bleiben. Live inros-lackner.de: 5 ×link-name-context→ 5 ×links/generic-name(Low); Detektionskorpus mit Chrome grün. -
Unreleased — Berichtsqualität nach dem inros-Review (Pläne 69–93, 94): Ein externes Review des Einzel- und des Sitemap-Berichts für inros-lackner.de wurde Punkt für Punkt gegen Code und Live-Seite geprüft; nur bestätigte Befunde sind geändert, widerlegte bleiben dokumentiert (
plan/referenz/2026-10-23-*). Messfehler: Best Practices und Tech-Stack meldeten ohne Erhebung 100 (jetzt Prüfstatus), CSS-Abdeckung hatte Zähler = Nenner (316/316 → 316/5076), die Viewport-Rechnung nutzte andere Modulgewichte als veröffentlicht, Security zog für kontextabhängige Header ab (30 → 40), drei WCAG-Kriterien fehlten in der Abdeckungsliste (manuell 22 → 25). Batch: Linkgraph ab der Startseite, gemessene Sitemap-Statusverteilung, Stichprobe offengelegt, Spannweite statt „sauberer Seiten“, Themenvergleich mit richtiger Einheit (similarity_percent,shared_termsals Alias) aus dem Hauptinhalt statt dem Body, Seitengruppen und gemeinsame Blocking-Dateien (5 statt „100“). Sprache: Befunde mit „möglicher Rechtsrelevanz“ statt „rechtlich relevant“, heuristische UX/Journey-Aussagen als solche, Mustermeldungen und Schema-Matrix lokalisiert. PDF: Befundmatrix mit vollen Titeln, keine leeren Seiten oder allein stehenden Überschriften mehr (renderreport 0.6.2), Batch-PDF scheiterte nicht mehr an PDF/UA.crossim Release-Workflow auf einen Commit gepinnt. Nachweis: Live-Läufe Einzel und Batch (20 von 685) vor/nach, gerenderte Seiten gesichtet. -
Unreleased — Sicherheitsmeldungen vom Oktober 2026:
rustls0.23.46,rustls-webpki0.103.16 undwebpki-roots1.0.11 im Lockfile (RUSTSEC-2026-0347/0348/0349, -0381, -0382);zip2 → 9 undc2pa0.90 → 0.91 (RUSTSEC-2026-0367/0368/0369, beide Kopien vonzip). Der Browser-Installer erkennt Verzeichniseinträge jetzt überZipFile::is_dir(), neuer Unit-Test zum Entpacken.cargo audit: keine Schwachstellen. -
Unreleased — Prüfhinweise, Bedienelement-Kontrast und Baumbereiche aus
a11y-rules0.23.0 (#722, #698, #700, #738; casoon/barrierlab#39, #21, #23, #41): Die sieben Prüfhinweis-Stubspointer_gestures,motion_actuation,no_timing,no_interruptions,re_authenticate,unusual_wordsundhelplaufen alsmanual/*im geteilten Bestand. WeiterUNTESTED, einmal je Seite, aber nur noch mit Anlass (Gesten-Widgets, Inline-Skript mit Bewegungssensoren, Felder, Live-Regionen, Anmeldung, Text). Der heuristische Teil vonhelp(jedes Textfeld ohnearia-describedbyals 3.3.5-Verstoß) entfällt.non_text_contrast_cssläuft alscontrast/control-insufficient/contrast/control-undetermined(1.4.11) mitappearance,border-top-*und eigener Fläche aus dem DOMSnapshot; fehlt der Hintergrund,UNTESTEDstatt Vergleich gegen Weiß. Gelöscht: acht Regeldateien; die alten Kennungen lösen überLEGACY_RULE_IDSundLEGACY_WCAG_MAPauf. Der CDP-Walk markiert jetzt Shadow Roots und per Slot eingesetzten Inhalt als eigene Baumbereiche, IDs gelten je Bereich.html-conform0.3.2 nimmt<legend>nach Leerraum im<fieldset>nicht mehr als ungültig. Live vorher → nachher (2026-10-23, AA, Desktop): upscale.casoon.dev/aufgaben/ Score 92 → 97 (html-content-modelam<legend>entfällt); bund.deids/duplicate1 → 0 (#content), sachsen-anhalt.de 2 → 0 (#panel,#card__heading) — IDs aus den Shadow Roots der muse-Komponenten, Scores unverändert. bundesregierung.de (AAA): neu je ein Hinweis für 2.5.1 (Slick-Karussell), 2.2.3, 2.2.4, 3.1.3 und 3.3.5; 2.2.5 und 2.5.4 ohne Anlass still. Detektionskorpus (Chrome) grün, Erwartungen auf die neuen Kennungen umgestellt. -
Unreleased — Zielgroesse, Reihenfolge, Endlos-Animation und Nur-Zeiger aus
a11y-rules(#698, barrierlab#49):targets/size(2.5.8 AA) undtargets/size-enhanced(2.5.5 AAA, nur was 2.5.8 nicht schon meldet) ersetzentarget-size-minimumundtarget-size;order/visual-mismatch(1.3.2) ersetztcss-order-reading-sequence;motion/infinite-animation(2.2.2) ersetzt den Animations- und<marquee>-Teil vonpause-stop-hide;keyboard/pointer-only(2.1.1,cursor: pointeroderonclickohne Rolle, tabindex und Bedienelement darin) ist neu. Geloescht:target_size_minimum,target_size_enhanced,meaningful_sequence,pause_stop_hide. Die alten Kennungen loesen ueberLEGACY_RULE_IDS(Regelfilter) undLEGACY_WCAG_MAP(Taxonomie) weiter auf. Urteil aendert sich: 2.5.8 und 2.5.5 warenFAIL, jetztREVIEW; ein beim Messen noch wachsendes Ziel bleibtUNTESTEDwie bisher. 1.3.2 und 2.2.2 waren und bleiben Pruefhinweise. Gewollt anders: keine Obergrenzen mehr (lokal 5 Befunde, 1000 Ziele, 5 Flex-Container — die Reihenfolge sah deshalb den Fussbereich von bundesregierung.de und die Kopfzeile von berlin.de nicht);flex-direction: *-reversezaehlt; jede endlose Animation zaehlt, auch kurze Durchlaeufe (2.2.2 gilt fuer Bewegung, die insgesamt laenger als fuenf Sekunden laeuft; lokal nur Durchlaeufe ueber 5 s),prefers-reduced-motionhebt den Befund nicht mehr auf, angehaltene Animationen zaehlen nicht (lokal schon). Nicht mehr geprueft: die Karussell-Signale vonpause-stop-hide(data-autoplay,data-ride,.swiper-autoplay-running,data-update-interval) — auf den sechs Referenzseiten ohne Treffer. Seit a11y-rules 0.22.0 zaehlt jedes Bedienelement als Ziel, unabhaengig vontabindex(Karussellpunkte mit rovingem tabindex: bundesregierung.de mobil#slick-slide-control00–03, 10 × 10 px, jetzt gemeldet; dazu 2.5.5 andiv[role=button]bundesregierung.de und#moreNewswetter.com, beide echte Ziele). Korpus: neues Urteilabsent(kein Befund welcher Art auch immer), weilpassnur Verstoesse zaehlt und beiREVIEW-Regeln nie scheitert; neuer Fallpointer_only. Live vorher → nachher (2026-10-05, Desktop und mobil, Verstoesse / Score): AA gov.uk 3 → 3, bundesregierung.de 18 → 13 (die 5 lokalen 2.5.8-Verstoesse, davon 4 Sprunglinks bei −99999 px, entfallen), wetter.com 137 → 137, n-tv.de 73 → 64 / 36 → 39, spiegel.de 100 → 98 / 24 → 25, berlin.de 31 → 31; AAA gov.uk 45 → 40 / 89 → 91, bundesregierung.de 87 → 74, wetter.com 229 → 223, n-tv.de 390 → 371 / 29 → 34, spiegel.de 347 → 341 / 21 → 22, berlin.de 61 → 53 / 38 → 41. Neu als Pruefhinweis: endlose Animationen auf spiegel.de (9) und bundesregierung.de (1), Reihenfolge auf berlin.de (2), bundesregierung.de und spiegel.de, 2.5.5 ohne Obergrenze (gov.uk 82 Eintraege statt 5 Verstoesse). -
Unreleased —
pointer_events_noneundanimating(#698, barrierlab#49):pointer_events_noneist das berechnetepointer-events: noneaus dem DOMSnapshot.animatingkommt aus einem Durchgang vor dem Snapshot wieobscured:document.getAnimations()mitplayStaterunningund endlichemendTime— damit auch laufende Uebergaenge —, endlose Animationen zaehlen nicht; ein Element gilt als animiert, wenn es selbst oder ein Vorfahre darin steht, nur im Hauptdokument. Die berechnetenanimation-*- Werte allein taugen dafuer nicht: Sie bleiben nach dem Ende einer Animation stehen. Gefuellt gesehen (2026-10-05, Desktop,Some(true)):pointer_events_nonebundesregierung.de 114, spiegel.de 120, gov.uk 6, berlin.de 2, Korpustarget_size_hidden_neighbours1;animatingbundesregierung.de 246, wetter.com 89, spiegel.de 23, Korpustarget_size_animation1 (a#slow). -
Unreleased — Layout-Felder gefuellt, Vergleich mit lokalen Heuristiken (#698):
RenderedCdpDocument::layoutliefert jetzt an jedem Element mit Layout-Objekt die Felder aus dem DOMSnapshot (neue berechnete Stileflex-direction,order,min-width,cursor,animation-name/-duration/-iteration-count/-play-state,top,scroll-padding-top):flex_reversed(Flex-Container mit*-reverse),order,min_width_px(auto→ 0, nur Pixelwerte),cursor_pointer,infinite_animation(paarweise ueber dieanimation-*-Listen: Name, Dauer > 0,infinite, nichtpaused) undhides_focus: eine fixierte oder klebende Leiste im Hauptdokument, mindestens 20 px hoch, halb so breit wie die Seite, niedriger als der sichtbare Bereich (ein Einwilligungsdialog ueber alles ist keine Leiste), die oberhalb vonscroll-padding-topbeginnt und tiefer reicht — fixiert an ihrer Lage im sichtbaren Bereich, klebend an ihremtop; nur die aeusserste.obscuredunveraendert (nur Elemente mit eigenem Text).focus_visiblebleibtNone: kein Durchgang dieses Abrufs bewegt den Fokus. Die Heuristiken laufen weiter nur im Hintergrund (nicht inSHARED_RULES); die lokalen Regeln bleiben. Gefuellt gesehen (2026-10-05, Desktop,Some(true)):flex_reversedgov.uk 4, spiegel.de 20;order≠ 0 berlin.de 4, bundesregierung.de 2, spiegel.de 2;min_width_px320 bundesregierung.de 6, wetter.com 2, berlin.de 2;
cursor_pointerauf allen sechs;infinite_animationspiegel.de 9, bundesregierung.de 1;hides_focusspiegel.de 1 (header.sticky), n-tv.de 1;obscuredwetter.com 190, n-tv.de 61, spiegel.de 32. -
Unreleased — Regelfilter gilt fuer die geteilten Regeln (#698):
[rules] disabled/enabled_onlyund--disable-rulegriffen bisher nur fuer die lokalen Regeln; seit Kontrast geteilt ist, schaltete--disable-rule color-contrastnichts mehr ab. Jetzt nimmtwcag::shared::retain_allowedeine geteilte Kennung heraus, wenn der Filter sie unter ihrem eigenen Namen oder einer alten Kennung ausLEGACY_RULE_IDSabschaltet (color-contrast→contrast/text-*,link-in-text-block,landmark-one-main, …, nur vollstaendig abgeloeste Regeln), und vermerkt sie alsdisabled_by_rule_filter; ebenso in iframes. -
Unreleased — Kontrast ueber Bildern wieder per Abtastung (#698, barrierlab#47/#48):
RenderedCdpDocumentliefertRendering::sampled_backdrop: ein Bildschirmfoto ueber den sichtbaren Bereich hinaus (captureBeyondViewport), auf die Kandidaten zugeschnitten und in CSS-Pixeln (scale = 1/devicePixelRatio), einmal in die Seite geladen; je Element die WCAG-Leuchtdichten im Kasten (Alpha gegen Weiss, hoechstens 2500 je Element auf einem Raster). Seitenweit hoechstens 60 Elemente, die groessten zuerst: eigener Text im Hauptdokument, Textfarbe bekannt, Hintergrund nicht bestimmbar, weder optisch verborgen noch ueberdeckt (beim Messen im sichtbaren Bereich verdeckt), bis 15000 CSS-Pixel Tiefe — tiefer bleibtUNTESTED(Speicher, Zeit; ein Canvas ist hoechstens 32767 px hoch). Zeitgrenzen: das Skript wartet hoechstens 5 s auf das Foto, die ganze Abtastung hoechstens 10 s; danach, oder wenn das Foto scheitert, bekommt kein Element einen Hintergrund und der Audit laeuft weiter. Die geteilte Regel urteilt darueber wie die frueher lokale (Median, 40. Perzentil):FAIL, bestanden oder Pruefhinweis. Die Integrationsteststest_image_contrast_pixel_samplingundtest_opacity_overlay_contrast_pixel_samplinglaufen wieder unveraendert gegen ihre alten Urteile. Die PDF-Zeile „Kontrast X (erforderlich Y)” liest die Messwerte der geteilten Regel (contrast_ratio,required_ratio, ohne:1geliefert). Abgetastet gesehen (2026-10-05, Desktop, ohne--dismiss-consent): berlin.de 14, gov.uk 0, bundesregierung.de 0, wetter.com 5, n-tv.de 39, spiegel.de 0 (die Bildnachweise liegen tiefer als 15000 px). Laufzeit des Audits vorher → nachher im Rauschen (−9 s bis +1,5 s je Seite). -
Unreleased — Kontrast aus
a11y-rules(#698, barrierlab#47):contrast/text-insufficient(1.4.3 AA),contrast/text-undetermined(1.4.3,UNTESTED) undcontrast/text-enhanced(1.4.6 AAA, nur mit--level aaa; meldet nur, was 1.4.3 besteht) ersetzencolor-contrast. Geloescht:wcag::rules::contrastsamt Seitenregel und Pixelvergleich mit dem Bildschirmfoto,accessibility::styles(extract_text_styles);Colorliegt jetzt innon_text_contrast_css(bleibtwcag::rules::Color). Die alte Kennung loest ueber die Taxonomie weiter auf (a11y.contrast.weak,axe_id: color-contrast); 1.4.6 hat einen eigenen AAA-Eintraga11y.contrast_enhanced.weakmit AAA-Gewicht (vorher zaehlten AAA-Verfehlungen als 1.4.3 mit dem vollen Abzug). Dunkelmodus und Farbsehschwaeche-Ansicht zaehlen die geteilten Verstoesse ueber einen frischen DOMSnapshot; der Vergleich hell/dunkel paart Elemente ueber die Backend-ID. Gewollt anders: Text unter einem fixierten oder klebenden Element (Cookie-Banner) istUNTESTED(#716 Fall 7); Text unteraria-hiddenwird gemessen (#395 nahm ihn aus). Vergleich geteilt gegen lokal (2026-10-05): Korpus (contrast_shadow_and_inline,gradient_text_contrast,low_contrast_text,text_fill_color_contrast) deckungsgleich, AA und AAA; gov.uk deckungsgleich; bundesregierung.de AA gleich, AAA +4 (Metanavigation, lokal als verborgen uebersprungen: absolut positionierte Liste in 0 px breitemnavmitoverflow: hidden); wetter.com ohne Consent-Overlay AA gleich (15), mit Overlay die zehn NavigationspunkteUNTESTEDstatt 2,37:1, AAA zusaetzlich der Consent-Dialog (lokal als verborgen uebersprungen) und Teaser-Beschriftungen mit eigener deckender Flaeche (lokal Pruefhinweis); n-tv.de AA beide ohne Verstoss, AAA 25 lokale Verstoesse unter dem Consent-Overlay jetztUNTESTED; spiegel.de AA +19 echte Verstoesse (4,04:1, Weiss auf #e64415 und umgekehrt), die die lokale Regel uebersah (Klassennameoverflow-hiddengalt ihr als verborgen), AAA entsprechend mehr. Korpus-Erwartungen auf die neuen Kennungen umgestellt. Live gegenmain(2026-10-05, mit Abtastung), Barrierefreiheit AA / AAA vorher → nachher: gov.uk 93 → 93 / 70 → 89, bundesregierung.de 49 → 49 / 46 → 49, wetter.com 22 → 22 / 18 → 20, n-tv.de 36 → 36 / 23 → 29, spiegel.de 28 → 24 / 20 → 21 (AA-Verstoesse vorher → nachher: 0 → 0, 0 → 0, 15 → 12, 25 → 23, 3 → 22; die Abnahmen sind Text unter dem Consent-Overlay, jetztUNTESTED). AAA steigt, weil 1.4.6 jetzt mit AAA-Gewicht zaehlt. Referenzseite berlin.de 45 → 49, knapp ueber dem Band 15–48: Die lokale Regel meldete Desktop 11 / mobil 12 Verstoesse, ueberwiegend Bildnachweise und Teaser-Titel ueber Fotos (p.image__copyright,a.title), gemessen gegen eine Vorfahrenfarbe (2,56:1 bzw. 1,00:1). Mit seitenweiter Abtastung ist keiner mehrUNTESTED; es bleiben 1 / 3 Verstoesse (daruntera.title, 4,02:1), der Rest besteht ueber den Fotos. Das Band ist neu zu bewerten. -
Unreleased — Optisch verborgener und ueberdeckter Text, Hintergrund ueber Bildern (Teil von #698, Host-Seite von barrierlab#47):
RenderedCdpDocumentliefertRendering::visually_hiddenaus demDOMSnapshot(neue Stilwerteclip,clip-path,text-indent,position,opacity):opacity: 0am Element oder einem Vorfahren,clip: rect(≤ 1px …)an einem absolut positionierten Kasten,clip-path: inset(≥ 50 %),text-indentab 999 px, ein Kasten von hoechstens 1 px mitoverflow: hidden|clip(am Element oder bis zwoelf Ebenen darueber; ein absolut positionierter Nachfahre entkommt einem statischen, ein fixierter jedem Kasten) und ein Rahmen ganz links oder oberhalb des Dokuments. VonLayoutfuellt das Dokument nurobscured, an Elementen mit eigenem Text im Hauptdokument: ein Skriptdurchgang vor dem Snapshot prueft perelementFromPointan der Mitte, ob ein fremdes fixiertes oder klebendes Element den Text verdeckt; nur die Treffer werden perdescribeNodeauf Backend-IDs abgebildet. Die effektive Hintergrundfarbe istNone, wenn ein malendes Geschwister des Elements oder eines Vorfahren (bis sechs Ebenen, bis zur ersten deckenden Flaeche) den Kasten ueberschneidet und eines von beiden absolut oder fest positioniert ist (#716 Fall 6: wetter.comh4.newsCarousel__headline, spiegel.defigcaption > p). Gefuellt gesehen (DoD 4, 2026-10-05,visually_hiddengemessen / wahr,obscuredgemessen / wahr): gov.uk 444/15, 132/0 (darunterbutton.gem-c-search__submit); bundesregierung.de 6706/5634 (fast alles SVG-Sprite-Pfade in einem 0-px-<svg>), 232/0; wetter.com 1897/569 (Hover-Menues mitopacity: 0), 577/199 mit Consent-Overlay bzw. 0 ohne; n-tv.de 3832/16, 1097/79; spiegel.de 6793/135, 1027/36. -
Unreleased — Statische Darstellungsregeln aus
a11y-rules0.19.1 (#699): Die Regeln der Darstellungskonvention, die nur das Markup brauchen, kommen jetzt aus dem geteilten Bestand (barrierlab#22):display/toggle-missing(ersetzt die lokale Regel gleichen Namens, derselbe Text) und die neuenviz/text-missing,viz/caption-missing,viz/static-missing,viz/table-missing(Letzteres ein Pruefhinweis). Die neuen Kennungen haben eigene Taxonomie-Eintraege (a11y.display_caption.missing,a11y.display_static.missing,a11y.display_table.review;viz/text-missingteilta11y.display_text_layer.missing), Erklaerungen und Score-Bereiche; wiedisplay/*gelten sie als Konventionsregeln, nicht als Rechtshinweis. Indisplay_modesbleibt, was nur die laufende Seite zeigt:display/init-missing(Beobachter vor der Navigation),display/text-hidden(samt berechneter Sichtbarkeit und der ausgeblendeten Figur, #725),display/text-media-visibleunddisplay/text-not-visible. Diese beiden Kennungen gibt es auch ina11y-rules, aber nur als statischer Teil; auditmysite uebernimmt sie nicht, damit eine Kennung nie zwei Quellen hat. Gewollt anders:display/text-not-visiblemeldet im Textmodus nur noch eine Textschicht, die Inhalt hat, aber nicht erscheint; fehlt sie oder ist leer, ist dasviz/text-missingund gilt in jedem Modus, nicht nur im Textmodus. Neue Corpus-Fixturedisplay_modes_viz_static, die Erwartung indisplay_modes_violationszieht aufviz/text-missingum. Geprueft: Unit-Tests, Detection-Corpus; Gegenprobe an geographia.eu (Referenzumsetzung, z. B.climate-monitormit 26 Figuren): alte und neue Fassung melden dort nichts, kein Fehlalarm auf konformen Seiten. -
Unreleased — Rendering-Schicht aus dem DOMSnapshot, drei Darstellungsregeln aus
a11y-rules(Teil von #698, Host-Seite von barrierlab#21 zweiter Teil;a11y-*0.20.0):RenderedCdpDocumentfuelltComputedStylejetzt vollstaendig aus demselbenDOMSnapshot.captureSnapshot: Text- und effektive Hintergrundfarbe (ueber die Vorfahren im flachen Baum verrechnet, zuunterst weiss;Nonebei Hintergrundbild, Verlauf oder unlesbarer Farbe), Schriftgroesse, -gewicht, -schnitt, -familie,list-style-type,text-decoration-line,border-bottom-style; durchsichtige Textfuellung (background-clip: text, #640) ergibt keine Farbe,display: contents(etwa<slot>) nimmt Farbe und Schrift vom gerenderten Text.bounds()liefert den Rahmen des Layout-Objekts,scroll_overflow_px()den Scroll-Ueberhang ausincludeDOMRects(scrollRects/clientRects, nur auf Achsen mitoverflow: auto|scroll). EinLayoutliefert das Dokument nicht; die Heuristiken darauf bleiben ungelaufen. Gefuellt gesehen (DoD 4, 2026-10-05, Elemente mit Layout-Objekt / alle): gov.uk 444/617, bundesregierung.de 6695/7591, wetter.com 1905/2612, n-tv.de 3636/4789, spiegel.de 6673/8891 — jedes neue Feld auf jeder Seite gefuellt. Uebernommen:redundant-role(<ul>/<ol>) →lists/role-redundant(4.1.2 A),link-in-text-block→color/link-indistinct(1.4.1 A; zusaetzlich zaehlen Schriftfamilie, Unterkante und Hintergrund als Unterscheidung),scrollable-region-focusable→keyboard/scrollable-region-not-focusable(2.1.1 A). Geloescht:redundant_role,use_of_color,scrollable_region; die alten Kennungen loesen in Taxonomie und Erklaerungen weiter auf. Die Farbsehschwaeche-Ansicht zaehltcolor/link-indistincteinmal statt je Modus (die Emulation aendert keine berechneten Stile). Korpus unveraendert erfuellt (redundant_role_list_style,link_in_text_block_context,scrollable_region_focusable,misc_content_checks). Die drei Regeln laufen nur im obersten Dokument wie die abgeloesten. Kontrast bleibt lokal:contrast/text-*deckt sich am Korpus mitcontrast(Shadow DOM, 4,46:1 ungerundet, Float-Container, Verlaufstext undeterminiert), live aber fehlen AAA-Schwellen, visuell versteckter Text wird gemessen (gov.uk-Suchknopf,text-indent: -5000px), und Text ueber positionierten Bildern (#716 Fall 6) wirdFAILstattUNTESTED(wetter.comnewsCarousel__headline, spiegel.de Bildnachweise). Live gegenmain(2026-10-05), Barrierefreiheit AA / AAA vorher → nachher: gov.uk 93 → 93 / 70 → 70, bundesregierung.de 49 → 49 / 46 → 46, wetter.com 22 → 22 / 18 → 18, n-tv.de 36 → 36 / 22 → 22, spiegel.de 26 → 26 / 20 → 20; die drei Regeln liefen vorher und nachher ohne Befund, die uebrigen Abweichungen (color-contrast±1,target-size8 → 10 auf AAA) sind Seitenwechsel zwischen den Laeufen in unveraenderten Regeln. -
Unreleased — Reduzierte Bewegung aus
a11y-rules0.19.1 (Teil von #698): Seit 0.19.1 loesta11y-rulesdie Kurzformanimationnach CSS Animations auf; Chromes Serialisierung (2s linear 0s infinite normal none running spin) ergibt wieder den Namenspinstattnone(Fehler aus diesem PR, Fix in barrierlab). Damit laeuftprefers-reduced-motion→motion/reduced-motion-ignored(2.3.3 AAA, Hinweis statt Verstoss, eine Meldung je Seite) ueber die Stylesheets;reduced_motionist geloescht, die alte Kennung loest in der Taxonomie weiter auf. 2.3.3 zaehlt als Hinweis-Kriterium (AAA). Korpus:media_and_motion,reduced_motion_transformundtarget_size_animationmelden den Hinweis,reduced_motion_overrideundreduced_motion_colour_onlynicht. Gewollt anders: Die abgeloeste Page-Rule lief trotz AAA schon ab--level aa(min_level: AA); der geteilte Hinweis erscheint wie die uebrigen AAA-Kennungen nur mit--level aaa. Live gegenmain(2026-10-04) auf AAA gleiche Treffer (bundesregierung.de und n-tv.de je 1, gov.uk, wetter.com, spiegel.de 0); auf AA faellt der bisherige 2.3.3-Befund weg, Barrierefreiheit AA / AAA vorher → nachher: gov.uk 92 → 93 / 67 → 70, bundesregierung.de 49 → 49 / 35 → 46, wetter.com 21 → 22 / 17 → 18, n-tv.de 22 → 31 / 18 → 22, spiegel.de 25 → 28 / 18 → 20. -
Unreleased — Regeln ueber Stylesheets aus
a11y-rules(Host-Seite von #698, barrierlab#21 erster Teil): auditmysite liest die Stylesheets der Seite (document.styleSheets, je Sheet dercssTextaller Regeln; fremde Sheets ohne lesbarecssRulesfehlen wie bisher), parst sie mitstylesheet-parse0.1.0 (neue direkte Abhaengigkeit) und gibt sie mit dem Dokument der geteilten Regeln ana11y_rules::run_stylesheets_in(accessibility::fetch_stylesheets,wcag::shared::run_shared_rules_with_stylesheets). Scheitert das Lesen, bleiben die RegelnNotRun::CapabilityMissing. Frames bekommen keine Sheets; die Regeln urteilen ueber die Seite wie die abgeloesten, die nur im obersten Dokument liefen. Uebernommen:focus-visible-outline-none→focus/outline-removed(2.4.7 AA, hoch; ein in:focuswieder gesetzter Rahmen zaehlt als Ersatz, sueddeutsche.de),css-orientation-lock(Stylesheet-Teil) →orientation/content-hidden(1.3.4 AA, Hinweis, nur Selektoren mit Treffer auf der Seite, ohne Pseudo-Elemente),visual-presentation(Blocksatz, Zeilenabstand) →text/justifiedundtext/line-height-tight(1.4.8 AAA, Hinweise, gemessen an den<p>der Seite). Geloescht:focus_visible_css; ausorientationdie Stylesheet-Suche (der berechnetetransform: rotateanbody/htmlbleibt), ausvisual_presentationdie Stylesheet-Suche (derUNTESTED-Vermerk zu Farbwahl und Spaltenbreite bleibt). 1.4.8 zaehlt als Hinweis-Kriterium (AAA). Nicht uebernommen wegen eines Fehlers ina11y-rules0.19.0:motion/reduced-motion-ignored– Chrome serialisiert die Kurzformanimationvollstaendig (2s linear 0s infinite normal none running spin), und die Regel nimmtnoneausanimation-fill-modeals Animationsnamen, jede Animation ueber die Kurzform faellt heraus (Korpusmedia_and_motion,target_size_animation).reduced_motionbleibt deshalb vorerst hier (mit 0.19.1 behoben, siehe oben). Abweichungen im Changelog vona11y-rules0.19.0. Korpus:media_and_motionmeldetfocus/outline-removedundorientation/content-hidden,text_and_layouttext/justified(die enge Zeilenhoehe dort steht an einem<div>, nicht an einem<p>). Live gegenmain(2026-10-04), Barrierefreiheit AA / AAA vorher → nachher, B4–B7 und Stylesheets zusammen: gov.uk 92 → 93 / 67 → 70, bundesregierung.de 49 → 49 / 35 → 43, wetter.com 21 → 22 / 17 → 18, n-tv.de 22 → 29 / 18 → 21, spiegel.de 25 → 28 / 18 → 20. Entfallen sind die Fehlalarmecss-orientation-lockauf bundesregierung.de und n-tv.de (Breakpoint-Markerbody:before, Schliessknopf) undvisual-presentationauf spiegel.de (Selektor-Praefix); neu auf AAAtext/line-height-tightauf gov.uk und bundesregierung.de. -
Unreleased —
<area>und selbststartender Ton ausa11y-rules(#696, Nachtrag): Mita11y-rules0.19.0 sieht die Sicht<area>und<audio>ohnecontrolsauch mit berechneten Stilen (UAdisplay: none; gemeldet aus diesem PR, Fix in barrierlab). Damit laufenarea-alt→images/area-alt-missing(1.1.1 A;aria-label/aria-labelledbyzaehlen) undbackground-audio→media/audio-autoplayueber den geteilten Bestand;image_input_rulesundbackground_audiosind geloescht.media/audio-autoplayist ein Pruefhinweis unter 1.4.2 (A) statt Verstoss unter 1.4.7 (AAA) und laeuft damit schon auf AA; Taxonomiea11y.audio_control.missing, 1.4.2 zaehlt als Hinweis-Kriterium (stand schon in der manuellen Liste). Beide laufen auch in Frames. Korpus:misc_content_checksundmedia_and_visualmelden unter den neuen Kennungen. -
Unreleased — Tabellen-, Dokument-, Sprach- und Rollenregeln aus
a11y-rulesstatt eigener (#697, B7): Zwoelf Kennungen laufen ueber den geteilten Bestand:th-has-data-cells→tables/header-without-data(samttables/data-undetermined,UNTESTED, fuer einen noch nicht dargestellten Zeilenvorrat, #654),td-headers-attr→tables/headers-attr-invalid,html-xml-lang-mismatch→document/lang-mismatch,language-of-parts→language/part-unmarked(neulanguage/part-undeterminedauf Seiten, die weder Deutsch noch Englisch sind),abbreviations→language/abbreviation-unexpanded(Hinweis statt Verstoss),meta-refresh→timing/meta-refresh(0 s ist keine Frist),heading-orderaussection_headings→headings/section-without-heading(Hinweis, nur Artikel und benannte Abschnitte),redundant-role→aria/role-redundant,title-only-description→names/title-only(Hinweis; Textfelder meldet weiterforms/title-only-label),content-on-hover-focus→patterns/tooltip-unreferenced. Geloescht:table_extended,language_extended,language_of_parts,abbreviations,content_on_hover,info_relationships,section_headings, die Baumregelnth-has-data-cellsundheading-order. Ersatzlos gestrichen:presentation-semantic-children– nach WAI-ARIA 1.2 nimmtrole="presentation"/"none"nur dem Element selbst die Semantik, nicht den Nachfahren; die Regel haette das APG-Menueleistenmuster (<li role="none">um Menuelinks) gemeldet (barrierlab: 187 Fehlalarme auf 48 Seiten), der Tabellenfall isttables/presentational-with-headers. Aussection_headingsentfallen die Gliederungsluecken (headings/skip-level) und „mehr als 10 Absaetze, weniger als 3 Ueberschriften” (kein Beleg). Im Host bleiben: ausredundant_rolenur<ul>/<ol>mitrole="list"– ob die Rolle ueberfluessig ist, haengt am berechnetenlist-style-type(#644), dena11y-domnoch nicht liefert (barrierlab#21); der Korpusfallol#numbered-listmeldet weiter. Austiming_adjustabledie seitenweitenUNTESTED-Vermerke fuer Skript-Fristen (2.2.1, jetzt als Page-Rule2.2.1/timing-adjustable) undtimeouts(2.2.6), die barrierlab der manuellen Checkliste zuweist. Die Darstellungsregeln aus barrierlab#22 (viz/*, statische Haelften vondisplay/*) sind nicht uebernommen:display/text-hidden,display/init-missingunddisplay/toggle-missingtragen indisplay_modesdieselben Kennungen, pruefen dort aber auch berechnete Sichtbarkeit und den Zeitpunkt vondata-display; ein Aufteilen derselben Kennung auf zwei Quellen ist ein eigener Schritt,display_modesbleibt unveraendert. Taxonomie: neua11y.language_of_parts.unmarked(3.1.2 AA;language-of-partshatte keinen Eintrag); 3.1.2, 2.4.10 und 3.1.4 zaehlen als reine Hinweis-Kriterien, 3.1.2 steht damit in der Liste der manuell zu pruefenden Kriterien (21 → 22,docs/PARITY_CONTRACT.jsonc, README). Tabellen-, Rollen-, Namens- und Tooltip-Regeln sowie Abkuerzungen laufen auch in Frames. Geprueft: Detection-Corpus vorher/nachher (Umbenennungen, Hinweise statt Verstoesse wie im barrierlab-Changelog,language/part-undeterminedaufinvalid_lang_codeundskip_link_language, neue Abschnittshinweise auf fuenf Faellen). Pruefung B4–B7 gesamt gegenmain(Detection-Corpus 76 Faelle auf AAA: elf Barrierefreiheits- Scores steigen um 1–9 Punkte, keiner faellt; Live-Seiten auf AA, Barrierefreiheit vorher → nachher: gov.uk 92 → 93, bundesregierung.de 49 → 49, wetter.com 21 → 22, n-tv.de 22 → 25, spiegel.de 25 → 28). Neu auf echten Seiten:names/title-only(spiegel.de 132 Hinweise statt 5 Befunden),frames/name-missing(n-tv.de 6 statt 2, darunter die drei Sportdaten-Rahmen mittitle=""),aria/role-redundant(wetter.com 9 statt 20 gedeckelte, ausgeblendete Menues zaehlen nicht mehr);navigation/location-missingund die anderen AAA-Hinweise laufen auf AA nicht. Referenzlauf und Score-Kalibrierung bleiben in ihren Baendern. -
Unreleased — Bild- und Medienregeln aus
a11y-rulesstatt eigener (#696, B6): Ueber den geteilten Bestand laufen jetztinput-image-alt→images/input-alt-missing,object-alt→objects/alt-missing,server-side-image-map→images/server-side-map(alle 1.1.1 A) undframe-title→frames/name-missing(2.4.1 A, hoch;a11y-rulesfuehrt zusaetzlich 4.1.2). Geloescht:server_side_image_map, ausimage_input_rulesdie Pruefungen fuer<input type="image">und<object>, ausmedia_rulesdie Rahmennamen und die Baumregelvideo-caption(unbenanntesapplication/img, benanntes dekoratives Element – barrierlab hat sie mangels Beleg bzw. wegensvg/name-missing,images/alt-missingundaria/attribute-prohibitednicht uebernommen). Gewollt anders:aria-labelledbyzaehlt als Alternative,<embed>wird nicht mehr geprueft, nur<iframe>(kein<frame>), keine Obergrenze je Seite; die gerenderte Groesse ≤ 1 px kenntframes/name-missingohne Geometrie nicht (nur Breite/Hoehe als Attribut).frames/name-missinghat erstmals einen eigenen Erklaerungstext (frame-titlefiel auf den 2.4.1-Text „Fehlende Sprungnavigation” zurueck). Im Host bleiben: die Untertitel-Pruefungvideo-caption(laedt die<track>-Datei ueber das Netz),frame-tested(fremde Rahmen kennt nur der Browser) undmedia-alt(1.2.8) – barrierlab verweist dafuer aufmanual/media-alternatives, die manuelle Checkliste fuehrt auditmysite noch nicht. Nicht uebernommen wegen eines Fehlers ina11y-rules0.18.0:images/area-alt-missingundmedia/audio-autoplaymelden mit berechneten Stilen nie, weil die Sicht alles mitdisplay: noneherausnimmt und das UA-Stylesheet das jedem<area>und jedem<audio>ohnecontrolsgibt (Korpusmisc_content_checks,media_and_visual).area-alt(image_input_rules) undbackground-audiobleiben deshalb vorerst hier. Bild- und Rahmenregeln laufen jetzt auch in Frames. Abdeckung: 2.4.8 (navigation/location-missing, seit B5 nur Hinweis) zaehlt nicht mehr als automatisch geprueftes AAA-Kriterium. Geprueft: Detection-Corpus vorher/nachher (nur die umbenannten Kennungen, Selektor jetzttag#id). -
Unreleased — Links- und Zeigerregeln aus
a11y-rulesstatt eigener (#695, B5): Mita11y-rules0.18.0 laufen drei Kennungen ueber den geteilten Bestand:click-events-have-key-events→keyboard/click-handler-not-focusable(2.1.1 A, hoch),link-as-button→links/used-as-button(4.1.2 A, niedrig),location→navigation/location-missing(2.4.8 AAA, niedrig). Geloescht:click_handlers,fake_navigation_link,locationsamt ihrenPAGE_RULES-Eintraegen; die alten Kennungen loesen in der Taxonomie und den Erklaerungstexten ueber die neuen auf. Gewollte Unterschiede (Changelog vona11y-rules): keine Obergrenze je Seite mehr (bisher 10 bzw. 20),<a onclick>ohnehrefist ein Klick-Handler statt Scheinlink,navigation/location-missingist ein Pruefhinweis statt Verstoss und sieht auch Navigation im Shadow DOM. Vonpointer_cancellation(2.5.2) ist der statische Teil (onmousedown/ontouchstartan Bedienelementen) geloescht – barrierlab hat ihn mangels Beleg nicht uebernommen –, der seitenweiteUNTESTED-Vermerk bleibt im Host: Die manuelle Checkliste (manual/*, barrierlab#39) hat keinen Punkt fuer 2.5.2, und auditmysite fuehrt sie noch nicht. Neu: Geteilte Kennungen oberhalb der geprueften Stufe (--level) werden herausgenommen und alsNotRun::Disabled(above_wcag_level) vermerkt (wcag::shared::retain_up_to_level), so wie die abgeloesten AAA-Regeln nur mit--level aaaliefen. Klick-Handler und Scheinlinks laufen jetzt auch in Frames. Geprueft: Detection-Corpus vorher/nachher (nur die umbenannten Kennungen,locationals Hinweis, zusaetzlichlandmark_main_in_shadow_root), Ausschluss-Integrationstests mit den ungedeckelten Zaehlungen (12 bzw. 22 ausgeschlossene Treffer). -
Unreleased — Landmark-, Tastatur- und Strukturregeln aus
a11y-rulesstatt eigener (#694, B4): Die Landmark-Pruefungen, die Tastaturerreichbarkeit, die Seite ohne Ueberschriften und zwei Musterpruefungen laufen ueber den geteilten Bestand (SHARED_RULESinsrc/wcag/shared.rs). Geloescht:region,landmark_granular(mitis_landmark) undbypass_blocks; auskeyboarddie beiden 2.1.1-Pruefungen; auspatterns/die Befundedialog-no-focusable,accordion-no-controls,accordion-trigger-not-buttonundaria-expanded-required– Mustererkennung und Journeys bleiben,PatternAnalysis.violationsist jetzt immer leer. Im Host bleibtkeyboard-trap(2.1.2): Hinweis je modalem Dialog und der seitenweiteUNTESTED-Vermerk brauchen echte Tastaturbedienung. Kennungen:landmark-unique→landmarks/not-unique;landmark-banner-is-top-level,landmark-contentinfo-is-top-levelundlandmark-main-is-top-level→landmarks/not-top-level;landmark-no-duplicate-bannerundlandmark-no-duplicate-contentinfo→landmarks/banner-duplicateundlandmarks/contentinfo-duplicate;region→landmarks/content-outside(alle 1.3.1 A);bypass(„No headings found”) →headings/none(2.4.1 A, samt #709);focusable-no-roleundkeyboard(„appears not keyboard-focusable”) →keyboard/focusable-no-roleundkeyboard/interactive-not-focusable(2.1.1 A);dialog-no-focusable→dialog/focusable-missing(2.4.3 A);accordion-no-controls→patterns/accordion-controls-missing(4.1.2 A). Ersatzlos entfallen, wie in barrierlab#17 begruendet:accordion-trigger-not-button(an Rollen ohnearia-expandedmeldet dasaria/attribute-not-allowed, anlink,tab,treeitemerlaubt WAI-ARIA den Zustand, fehlender Fokus istkeyboard/interactive-not-focusable) undaria-expanded-required(riet ein Aufklappmenue aus dem Wort „menu”/„Menü” im Namen). Gewollte Unterschiede (Einzelheiten im Changelog vona11y-rulesund an den Eintraegen inSHARED_RULES):landmarks/not-uniqueundpatterns/accordion-controls-missingsind Pruefhinweise statt Verstoesse; doppelte banner/contentinfo zeigen auf die zweite Landmark statt die erste;landmarks/content-outsidemeldet einmal je Block statt je Textknoten;keyboard/focusable-no-rolezaehlt nur die Tabfolge und keinen benannten scrollbaren Bereich;keyboard/interactive-not-focusablenimmt deaktivierte Felder, native<option>,aria-activedescendantund Inertes aus;dialog/focusable-missingsucht in allen Nachfahren und prueft kein geschlossenes<dialog>;headings/nonezaehltrole="heading"mit. Die Landmark-Rolle kommt aus dem Markup statt aus Chromes Rolle (#639, #727). Ausschluesse (#726): Die geteilten Zaehlregeln (landmarks/not-unique,landmarks/main-duplicate,landmarks/*-duplicate) lesen bei aktivem--exclude-selectordas Dokument ohne die ausgeschlossenen Teilbaeume (ExclusionScope::without_excluded_dom,wcag::shared::adopt_page_counts); die AX-Variantewithout_excludedund der Parametercountedvoncheck_all_excludingentfallen,exclusion.excluded_landmarkszaehlt wie bisher. In Frames laufen jetztkeyboard/focusable-no-roleundkeyboard/interactive-not-focusable(bisher die Baumregelkeyboard) sowie neudialog/focusable-missingundpatterns/accordion-controls-missing; die Landmark-Regeln undheadings/nonebleiben beim Hauptdokument. Taxonomie: die Kennungen zeigen auf die Eintraege der abgeloesten Regeln, deren Legacy-Kennungen weiter aufloesen (headings/noneauf den vonbypass); neua11y.landmark_nested.invalidunda11y.accordion_controls.missingsamt Erklaerungstexten. Mita11y-rules0.17.0. Geprueft mit Unit-Tests, Detection-Corpus, Score-Kalibrierung, Referenzlauf und den Integrationstests zu Frames, Ausschluessen und Landmarks (Chrome) sowie einem Vorher/Nachher-Lauf auf 14 Live-Seiten; Referenzbaender gov.uk (75–95) und bundesregierung.de (35–76) neu bewertet (je +1: Inhalt ausserhalb von Landmarks je Block statt je Textstueck, Dialog-Fokusziel auch in tieferen Nachfahren). -
Unreleased — Fehlalarme aus upscale.casoon.dev (#739, #740): Das Dark-Mode-Modul meldete eine reine Dunkel-Seite (
html.darkfest gesetzt,color-scheme: dark, kein helles Layout) alsno_dark_mode_supportmit Score 50. Neu ist das Signal “dunkel im Standard-Rendering”: wirksamer Seitenhintergrund (body, sonst html, sonst die Leinwand nachcolor-scheme) dunkel und Body-Text hell; die Farben werden ueber ein Canvas-Pixel gelesen, damit auchoklch()aus Tailwind v4 zaehlt. Eine solche Seite gilt als unterstuetzt (detection_methods: “Dark by default (no light view)”), ohneno_dark_mode_supportund ohne den Klassen-Hinweis (#739). Das Mobile-Modul nahm als kleinste Schrift auch Elemente ohne lesbaren Text; KaTeX’span.vlist-s(ein Nullbreiten-Leerzeichen bei 1 px) ergab “Smallest font size is 1.0px”. Fuer die kleinste Schrift zaehlen jetzt nur Elemente mit eigenem Text aus mehr als Leerraum und Nullbreiten-Zeichen (#740). Live: upscale.casoon.dev Dark-Mode 50 → 85, Pythagoras-Lektion kleinste Schrift 1 px → 11 px (echter Text); gov.uk (hell) weiter ohne Dark-Mode-Unterstuetzung, casoon.de unveraendert. #738 (<legend>nach Leerraum) liegt in html-conform: casoon/barrierlab#41. -
Unreleased — Formularregeln aus
a11y-rulesstatt eigener (#693, B3): Die Formular-, Kontextwechsel- und Captcha-Pruefungen laufen ueber den geteilten Bestand (SHARED_RULESinsrc/wcag/shared.rs), die abgeloesten eigenen Regeln sind geloescht:form_rules,error_identification,input_purpose,identify_purpose,redundant_entry,label_title_only,on_focus,instructionsundlabels(check_form_control, der letzte Rest der Datei). Im Host bleibt, was die laufende Seite braucht: inon_inputdas Nachschlagen einer aufgerufenen Funktion ueberwindow(Verstoss, wenn erst sie den Kontext wechselt) und die Namensvermutung („Language”) ohne Absende-Button, beide jetzt unter derselben Kennung wie die geteilte Regel (context/on-input), damit der Verstoss deren Pruefhinweis am selben Element verdraengt; inaccessible_authenticationder Einfuege-Test (accessible-auth-paste-blocked). Kennungen:label„no accessible label” undcontrol-missing-labelan Formularfeldern →forms/label-missing(3.3.2 A);label„Placeholder used as only label” →forms/placeholder-as-label;label„Form group has no legend or label” →forms/group-name-missing;label„Required field not clearly indicated” und „may not indicate required status” →forms/required-unmarked;label„may require format instructions” →forms/instructions-missing(alle 3.3.2 A);autocomplete-valid→forms/autocomplete-invalid(ungueltiger Wert) undforms/purpose-missing(fehlender Wert, 1.3.5 AA), dazuidentify-purpose(1.3.6 AAA) →forms/purpose-missing;input-error-messageundaria-invalid-without-describedby→forms/error-unidentified(3.3.1 A);form-field-group→forms/group-missingundlabel-title-only→forms/title-only-label(1.3.1 A);form-no-submit→forms/no-submit(3.2.2 A, samt #728);redundant-entry→forms/redundant-entry(3.3.7 A);input-no-context-change(Handler im Markup) →context/on-input(3.2.2 A);focus-no-context-change→context/on-focusundcontext/autofocus(3.2.1 A);accessible-auth-captcha→auth/captcha(3.3.8 AA). Gewollte Unterschiede (Einzelheiten im Changelog vona11y-rulesund an den Eintraegen inSHARED_RULES): ein fehlendes Label, eine fehlende Fehlerbeschreibung, ein unmarkiertes Pflichtfeld und ein fehlender Eingabezweck stehen einmal statt zweimal im Bericht;forms/purpose-missing,forms/required-unmarked,forms/title-only-label,context/on-focusundcontext/autofocussind Pruefhinweise statt Verstoesse;forms/autocomplete-invalidprueft die ganze Autofill-Grammatik und ist niedrig statt mittel;context/on-inputprueft auch Optionsfelder;forms/no-submitzaehlt perform-Attribut zugeordnete Felder und Buttons mit; der seitenweiteUNTESTED-Vermerk zu 1.3.6 entfaellt. Die Formularregeln laufen jetzt auch in Frames, auch die bisher nur im Hauptdokument geprueften (autocomplete, Absenden, Wiederholung, Kontextwechsel, Captcha). Taxonomie: alle Kennungen zeigen auf die Eintraege der abgeloesten Regeln, deren Legacy-Kennungen weiter aufloesen; neue Erklaerungstexte fuerforms/no-submitundauth/captcha. Mita11y-rules0.16.0, das ein unbenanntes ARIA-Formularfeld (combobox, listbox, textbox, …) wie das native Feld ohne Label als kritisch meldet. Geprueft mit Unit-Tests, Detection-Corpus, Score-Kalibrierung und Referenzlauf (Chrome) sowie einem Vorher/Nachher-Lauf auf 14 Live-Seiten. -
Unreleased — SEO-Ueberschriften aus den geteilten Regeln, kein zweiter HTML-Validator (#724): Die SEO-Ansicht pruefte fehlende und mehrfache H1, uebersprungene Ebenen und leere Ueberschriften ein zweites Mal. Diese Befunde kommen jetzt aus
headings/*(a11y-rules) der Barrierefreiheitspruefung, nach Ausschluessen und einmal je Befund ueber beide Viewports;SeoModule::derivesetzt sie vor die SEO-eigene Regel fuer lange Ueberschriften und berechnet Score, Content-Profil und SERP-Sicht neu. Die SEO-Ueberschriftenliste (h1_count,h1_text, Laengen) liest jetzt auch offene Shadow Roots, damit sie zur Sicht der geteilten Regeln passt (dm.de: die h1 des Usercentrics-Banners im Shadow DOM zaehlt mit; SEO-Score 75 → 67). Der lokale html5ever-Check (validate_html_locally) ist gestrichen: Er zaehlte Parse-Fehler desselben serialisierten Live-DOM, denhtml_conformgruendlicher prueft. Der Validator-Status im SEO-Teil heisst jetztdelegatedund verweist auf html-conform (--full,--html-conform);ParseErrorsbleibt nur, damit gespeicherte Berichte laden. Gegenprobe alt gegen neu mit--seo: casoon.de, gov.uk, berlin.de und mit.edu mit gleichen Ueberschriftenbefunden und gleichem Score. -
Unreleased — CSP ueber
csp-parse(#723):src/securityzerlegte die Content-Security-Policy selbst (split(';')); jetzt liest es sie mitcsp-parse0.1.0 aus barrierlab (CSP3: Policy-Liste, Direktiven, Source-Listen mit Schluesselwoertern, Nonces, Hashes, Host-Wildcards). Die Bewertung (welche Schwaeche wie schwer wiegt) bleibt hier. Gewollte Unterschiede: mehrereContent-Security-Policy-Header werden alle gelesen und als Policy-Liste verbunden statt nur der erste; in einer Policy-Liste zaehlt eine Schwaeche nur, wenn jede Policy sie hat (der Browser setzt alle durch); eine doppelte Direktive behaelt ihren ersten Wert statt des letzten; Nonce und Hash zaehlen nur mit gueltigem Base64-Wert; die Wildcard-Pruefung ueber alle Direktiven liest nur Source-Listen (nichtreport-uri,sandboxu. a.). Neue Unit-Tests fuer die drei Faelle; Security-Corpus gruen; die acht Referenzseiten liefern mit altem und neuem Parser dieselben CSP-Befunde. -
1.7.3, 2026-10-01: ARIA- und Namensregeln kommen jetzt aus
a11y-rulesstatt aus eigenen Implementierungen (#691 B1, #692 B2, a11y-rules 0.15.0), mit neuen Kennungen nach dem Schemaaria/*,names/*,dialog/*,popover/*,inert/*; ein unbenannter Dialog steht nur noch einmal im Bericht. Cross-Site-iframes werden geprueft, weil der Audit-Browser ohne Site-Isolation startet (#720). SPA-Portale werden erst nach dem ersten gerenderten Inhalt aufgenommen (#718). Fehlalarme behoben: Textebene in verborgenen Panels (#725), unbenannteform/regionals Landmark (#727), Landmark-Zaehlung trotz Ausschluss (#726, neues JSON-Feldexclusion.excluded_landmarks),form-no-submitbei reinen Schalter-Formularen (#728). Referenzlauf (reference_sites_test) und Detection-Corpus gruen. Einzelheiten in den Eintraegen darunter. -
Namensregeln aus
a11y-rulesstatt eigener (#692, B2): Die Pruefungen auf fehlende und unzureichende zugaengliche Namen laufen ueber den geteilten Bestand (SHARED_RULESinsrc/wcag/shared.rs), die abgeloesten eigenen Regeln sind geloescht:aria_naming_rules,dialog_rules,summary_name,status_messages,label_in_nameund ausaccessible_namealles ausserdescription-duplicates-name(#713) – die bleibt, weila11y_domkeine Accessible Description liefert; ihr Schalter inwcag::engineheisst jetzt so stattaria-label. Ebenfalls entfallen:aria-dialog-nameinpatterns::modal_dialogund der Dialog-Teil des fehlenden Namens inmodern_attributes(Menue und Popover bleiben dort). Kennungen:aria-command-name(ohne Button unda[href]),aria-input-field-name,aria-meter-name,aria-progressbar-name,aria-toggle-field-name,aria-treeitem-nameundaria-label„kein Name” →names/required-missing;aria-label„Icon Only” →names/symbol-only;aria-dialog-nameunddialog-name→dialog/name-missing;dialog-name„Dialog Modal” →dialog/modal-unmarked;summary-name→summary/name-missing;aria-live-region-role→status/live-overridden(4.1.3 AA);label-content-name-mismatch→label-in-name/mismatch(2.5.3 A); alle uebrigen unter 4.1.2 A. Schwere wie bisher. Gewollte Unterschiede (Einzelheiten im Changelog vona11y-rulesund an den Eintraegen inSHARED_RULES): ein unbenannter Dialog steht einmal statt bis zu dreimal im Bericht, ein unbenanntes Widget einmal statt zweimal; unbenanntemenu,tabund native<option>sind kein Befund mehr (ARIA 1.2 verlangt dort keinen Namen);meterundprogressbarstehen unter 4.1.2 statt 1.1.1; nicht fokussierbare unbenannte Widgets sind ein Verstoss wie bisher inaria_naming_rules, die zusaetzliche Warnung ausaccessible_nameentfaellt; der rein symbolische Name, das fehlendearia-modalund eine umgestellte, aber nicht abgeschaltete Dringlichkeit einer Live-Region (alertmitpolite) sind Pruefhinweise statt Verstoesse;dialog/modal-unmarkedprueft nurrole="dialog", kein natives<dialog>;summary/name-missingmeldet nur die<summary>, nicht zusaetzlich das<details>; leeresaria-labelledbyundaria-describedbyan einem benannten Element sind kein Befund mehr;label-in-name/mismatchprueft alle Rollen mit Namen aus dem Inhalt und Namen ausaria-labelledby, nicht nur Buttons mitaria-label, ohne den Deckel von 50 – und laeuft jetzt auch in Frames. Neuer Taxonomie-Eintraga11y.dialog_modal.missingsamt Erklaerung (bisher teilte das fehlendearia-modalKennung und Titel mit dem fehlenden Dialognamen); die uebrigen Kennungen zeigen auf die Eintraege der abgeloesten Regeln, deren Legacy-Kennungen weiter aufloesen. Geprueft mit Unit-Tests, Detection-Corpus und Score-Kalibrierung (Chrome). Mita11y-rules0.15.0. Der #527-Filter (Verstoss verdraengt Hinweis) vergleicht jetzt die Backend-Node-ID, wo beide Befunde eine tragen: geteilte Befunde haben kurze Selektoren (button), und ein Verstoss an einem Button verschluckte den Hinweis an einem anderen. Referenzband bundesregierung.de neu bewertet (35–72): drei Label-in-Name-Fehlalarme an per CSS verstecktem Text entfallen, der Cookie-Dialog ohne aria-modal ist ein Hinweis. -
Frames in fremden Prozessen (#720): Nach #715 blieben iframes, die Chrome wegen Site-Isolation in einem eigenen Renderer zeigt, ungeprueft (
skipped: cross_origin) — etwa der Webchat auf gov.cy (digital-assistant.gov.cy,gov.cyist ein Public Suffix), in dem axe einen unbenanntenrole="dialog"meldet. chromiumoxide 0.8 legt fuer solche Frames zwar intern eine Session an, bietet aber keine Befehle an sie. Statt eines eigenen CDP-Clients startet der Audit-Browser jetzt ohne Site-Isolation (--disable-site-isolation-trials,IsolateOrigins/site-per-processaus): Cross-Site-Frames laufen im Prozess der Seite, der bestehende Frame-Durchgang erreicht sie samt Anreicherung, Selektoren und Ausschluessen. Das Profil ist frisch und ohne Nutzerdaten; ignoriert ein kuenftiges Chrome die Flags, steht der Frame wieder sichtbar alscross_originim Bericht. Der Chrome-Test erwartet den Cross-Site-Frame jetzt als geprueft mitdialog-name-Befund; gov.cy live: Frame geprueft, Dialog gefunden. -
SPA-Portale: Aufnahme vor dem ersten Rendern (#718): eesti.ee zeigt nach dem Laden einen leeren Splash-Screen, waehrend die Angular-App ihre erste Ansicht holt — rund 400 ms ohne eine einzige DOM-Mutation.
wait_for_page_stabilitywertete 200 ms Ruhe als stabil, der AX-Baum wurde vor der ersten Ueberschrift gelesen undbypassmeldete “No headings found”. Ruhe ohne Inhalt (keinmain, keineh1-h3, kein sichtbarer Text) gilt jetzt nicht mehr als stabil; die Provenienz vermerkt das Warten alsreason, eine Seite ohne Inhalt bis Budgetende endet alsbudget_exhausted. Neuer Chrome-Test mit Splash-Fixture (scheitert ohne den Fix); Detection-Corpus gruen; eesti.ee und latvija.gov.lv ohne den Fehlbefund. -
ARIA-Regeln aus
a11y-rulesstatt eigener (#691, B1): Die ARIA-Pruefungen laufen ueber den geteilten Bestand (SHARED_RULESinsrc/wcag/shared.rs, alle unter 4.1.2 A), die abgeloesten eigenen Regeln sind geloescht:aria_allowed_attr,aria_prohibited_attr,aria_valid_attr_value,aria_required_parent,parsing,aria_roles,widget_rules, die Popover- und Dialog-Teile vonmodern_attributesund die Tab-Meldung auspatterns::tab_list. Kennungen:aria-roles→aria/role-invalid(neu dazuaria/role-abstract) und, fuer die Bestandteile,aria/required-children-missing;aria-attr-name-invalid→aria/attribute-unknown;aria-valid-attr-value→aria/attribute-value-invalid(die IDREF-Haelfte istaria/reference-missing);duplicate-id-aria→aria/owns-conflict;aria-allowed-attr→aria/attribute-not-allowed;aria-prohibited-attr→aria/attribute-prohibited;aria-required-parent→aria/required-parent-missing;aria-tab-selected-stateundtab-no-aria-selected→aria/tab-selected-missing;aria-tablist-tabpanel→aria/tabpanel-missing;aria-combobox-options→aria/combobox-popup-missing; ausmodern-attribute-misuse→popover/target-missing,popover/target-invalid,inert/dialog-inert. Gewollte Unterschiede (Einzelheiten im Changelog vona11y-rulesund an den Eintraegen inSHARED_RULES): Kontext und Bestandteile werden am DOM geprueft –ul[role=tablist] > li > a[role=tab](#715) meldet jetzt wie axe fehlende Bestandteile an der Tabliste und fehlenden Kontext am Tab; erlaubte und verbotene Attribute urteilen ueber die implizite Rolle mit (<div aria-expanded>ist ein Befund); Bestandteile nur fuer Behaelter mit expliziter Rolle, die native Tabelle nur mit<caption>ist kein Befund mehr; fehlender ausgewaehlter Tab und fehlendes Tab-Panel sind Pruefhinweise, der Tab-Hinweis steht einmal an der Tabliste statt je Tab; unbekannte Werte vonaria-currentundaria-invalidsind kein Befund; das sichtbarerole="menu"mitinertist keiner mehr. Inmodern_attributesbleiben der fehlende Name offener Dialoge, Menues und Popover, das offene Popover mitinertund der Fokus in einem inerten Teilbaum. Chromes eigene Rollennamen erreichen die geteilten Regeln nicht mehr (CdpDocument):imagewirdimg, die gross geschriebenen (LayoutTable*,DescriptionList,LabelText, …) werden zu „keine Rolle” – sonst bliebrole="img"ohne Attributurteil und brach eine Layouttabelle jede Kontextpruefung ueber ihr ab. Neuer Taxonomie-Eintraga11y.tab_selected.missing; die uebrigen Kennungen zeigen auf die Eintraege der abgeloesten Regeln, deren Legacy-Kennungen weiter aufloesen. Erklaerungen und Detection-Corpus sind nachgezogen. Geprueft mit Unit-Tests und dem Detection-Corpus (Chrome), darin der #715-Fall im iframe. Referenzbaender bundesregierung.de (35–65), berlin.de (15–48) und dm.de (15–48) neu bewertet: eingeklapptesaria-controlsohne Ziel ist kein Befund mehr (dm.de), Tab-Auswahl nur noch einmal je Tablist (ARIA SHOULD), Swiper-Listen alslists/invalid-structure(Medium) stattaria-roles(High) — a11y-rules 0.14.1 faengtli[role=group]dafuer wieder ab. -
Fehlalarme aus barrierlab.eu und geographia.eu (#725-#728):
display/text-hiddenmeldete die Textebene von Visualisierungen in inaktiven Bereichs-Panels (hiddenamsection) als entzogen; die ganze Figur samt Grafik war aber verborgen. Eine nicht gerenderte Figur zaehlt jetzt auch beihidden/aria-hidden/inertan Figur oder Vorfahr nicht als Verstoss (#725; die sr-only-Vermutung im Issue traf nicht zu,checkVisibilityliess das Clip-Muster schon durch).landmark-uniqueund die Top-Level-Pruefungen zaehlenformundregionnur mit zugaenglichem Namen als Landmark (#727). Die zaehlenden Landmark-Regeln (landmark-unique, doppeltes banner/contentinfo) lesen einen Baum ohne ausgeschlossene Teilbaeume; das JSON nennt die herausgenommenen Landmarks alsexclusion.excluded_landmarks, die PDF-Notiz ebenso (#726).form-no-submitmeldet ein Formular ohneaction, ohne Textfeld und ohne Absende-Element (nur Skript kann es absenden, typisch Schalter fuer Inhalte) nur noch als niedrigen Pruefhinweis statt als 3.2.2-Verstoss (#728). Neue Corpus-Faelledisplay_modes_hidden_panel,form_toggles_without_submit; Detection-Corpus gruen; Gegenprobe gegen beide Live-Seiten ohne die Fehlbefunde. -
1.7.2, 2026-09-30: Fehlalarm-Korrekturen und Luecken aus dem axe-Gegencheck der barrierlab.eu-Erhebung (21 EU-Portale) und aus geographia.eu: Zielgroesse nur fuer sichtbare Ziele im fertigen Layout (#705, #706), Ausschluesse auch in der Screenreader-Schicht und im Cache (#703, #708), Darstellungsregeln ohne Rechtsurteil (#704),
mainund Sprunglink hinter offenem Dialog bzw. Cookie-Banner (#709, a11y-* 0.13.5), Linkfarbe nur im Fliesstext (#710), Bewegung nur mit Beleg (#712), title-only und doppelte Beschreibung als Best Practice (#711, #713). Neu: Kontrast in Shadow DOM und ueber Float-Containern (#716), Regelscrollable-region-focusable(#717), elementbezogene Regeln in iframes samt Frame-Abdeckung im JSON (#715, teilweise; Frames in fremden Prozessen folgen in #720), geringeres Gewicht fuerzoom/viewport-missing(#702). Referenzlauf (reference_sites_test) und Detection-Corpus gruen. Einzelheiten in den Eintraegen darunter. -
Regeln laufen in iframes (#715): Ein
role="dialog"ohne Namen in einem iframe (gov.cy) blieb unentdeckt, weilgetFullAXTreeund die geteilten DOM-Regeln nur das Hauptdokument sahen; axe prueft jeden Frame. Neu ist ein Frame-Durchgang (audit::frames): Fuer jedes im Prozess der Seite gerenderte iframe (auch verschachtelt) holt er den AX-Baum perframeIdund baut aus demcontent_documentdesselbenDOM.getDocument-Abrufs ein eigenes Dokument, dann laufen dort die elementbezogenen Baum- und geteilten Regeln (Namen von Dialogen, Bildern, Links, Buttons, Formularfeldern; Rollen samt geforderten Eltern/Kindern; Listen- und Tabellenstruktur; referenzierte doppelte IDs;aria-hidden-Fokus u. a.) — per expliziter Positivliste. Seitenbezogene Regeln (Landmarks,h1, Titel, Viewport, Sprunglink,bypass,region) bleiben wie bei axe dem Hauptdokument vorbehalten. Befunde tragen den Selektor"{iframe} [frame] {element}"und eineframe:-Knotenkennung, Ausschluesse (#645) gelten auch hier. Uebersprungen werden verborgene Frames (hidden,aria-hidden,role=none, unsichtbar, hoechstens 1 px, im AX-Baum ignoriert), ausgeschlossene und prozessfremde (cross-origin) Frames;execution.framesim JSON fuehrt geprueft und uebersprungen samt Grund (cross_origin,hidden,excluded,ax_tree_failed,document_unavailable) je Viewport. Der JavaScript-Scaniframe/same-origin-contentprueft nur nochhtml-has-lang, seine uebrigen Pruefungen deckt der Durchgang ab. Neuer Detection-Corpus-Falliframe_widget_rulesund ein Chrome-Integrationstest, der auch belegt, dass keine Seitenregel im Frame meldet. Offen bleibt die Tablist ausul/li(magyarorszag.hu): Chrome glaettet daslizwischen Tablist und Tab im AX-Baum, die baumbasierten Regeln sehen dort — auch im Hauptdokument — eine gueltige Struktur. Live auf gov.cy liegt der Dialog in einem prozessfremden Frame (digital-assistant.gov.cy,gov.cyist ein Public Suffix): Er steht jetzt alscross_originuebersprungen im Bericht, geprueft wird er erst mit einer eigenen CDP-Sitzung je Out-of-Process-Frame. -
Screenreader-Ausschluesse ueberleben den Cache (#708): Bei
--reuse-cachewird der Screenreader-Bericht aus dem gespeicherten AX-Baum neu gebaut; die in #703 ausgeschlossenen Knoten standen danach wieder im Sidecar und im PDF. Die ausgeschlossenen Knoten-IDs liegen jetzt im Cache-Eintrag (snapshot.json,screen_reader_excluded_node_ids) und werden beim Wiederaufbau erneut angewendet. Aeltere Eintraege ohne das Feld verhalten sich wie bisher. -
Kontrast in Shadow DOM und ueber Float-Containern (#716): Zwei Luecken gegenueber axe. (1) Die Kontrastpruefung lief nur ueber den Light DOM; Text in Webkomponenten (Cookie-Banner
p-cookie-bannerauf verwaltung.bund.de, 3,17:1) fehlte. Sie geht jetzt durch offene Shadow Roots, der Hintergrund wird ueber den zusammengesetzten Baum bestimmt (Slot, Shadow-Host), Text direkt unter einem Shadow-Host nimmt Farbe und Schrift von seinem Slot (ohne Slot wird er nicht gerendert und nicht geprueft),aria-hiddenam Host wirkt nach innen, und der Selektor lautethost >>> inner(auch in der Pixelprobe). Die Overlay-Erkennung sieht jetzt auch positionierte Geschwister, die ein Bild enthalten (Hero-Bild als<p-responsive-image>), bis sechs Ebenen hoch; die Pixelprobe meldet verdeckten Text (Banner ueber dem Text) als Pruefhinweis statt ihn gegen den Banner zu messen. (2)__amsIsVisuallyHiddenhielt jeden Vorfahren ohne Hoehe fuer versteckt; eindiv, das nur Floats enthaelt, ist aber 0 px hoch und zeigt seinen Inhalt. Nur eine Box, die ihren Ueberlauf abschneidet, versteckt ohne Flaeche etwas; Nicht-Gerendertes erkenntcheckVisibility(). Damit wird der 4,46:1-Link auf slovensko.sk gemeldet (axe: 4,45:1; der Vergleich mit der Schwelle war schon ungerundet). Der Helfer wird auch von Design-Qualitaet, Journey und Medienregel genutzt. Live: bund.de meldet den Datenschutz-Link und den weissen Hero-Text ueber dem Foto (Pixelprobe 3,64:1), slovensko.sk den Link. Neuer Detection-Corpus-Fallcontrast_shadow_and_inline. -
Scrollbare Bereiche ohne Tastaturzugang (#717): Neue Regel
scrollable-region-focusable(2.1.1, A) →a11y.scrollable_region_focus.missing: ein sichtbares Element mitoverflow: auto|scroll, das mehr als 13 px ueberlaeuft (wie axe), Text enthaelt und weder selbst noch ueber einen Nachfahren per Tastatur fokussierbar ist. Chrome macht solche Bereiche seit Version 130 selbst fokussierbar, andere Browser nicht — deshalb bleibt es ein Befund. Neuer Detection-Corpus-Fallscrollable_region_focusable. -
Gewicht von
zoom/viewport-missing(#702): Desktop-Browser ignorieren das viewport-Meta-Tag, Zoomen per Geste bleibt moeglich; die Folge trifft nur Mobilgeraete. Der Score-Abzug sinkt von 2,5/5 auf 1,5/3, Bericht und Erklaerung nennen die Folge (Desktop-Breite, verkleinert, waagerechtes Scrollen beim Vergroessern). Befundstufe (FAIL) und Schwere kommen weiter ausa11y-rules; ob es dort ein Pruefhinweis sein sollte, ist eine Frage an barrierlab. -
Farbe als Linkmerkmal nur im Fliesstext, Bewegung nur mit Beleg (#710, #712):
link-in-text-block(1.4.1) prueft einen Link nur noch, wenn er im Fliesstext steht — wie axeisInTextBlock: Der Abschnitt seines Blocks (begrenzt durch<br>/<hr>) muss mehr Nicht-Link-Text als Link-Text enthalten, und zwar mindestens zwei Woerter. Links innavbzw. Menues und Logo-Links (Bild/SVG im Link, Logo-Container) fallen heraus. Damit melden Navigations-, Header-, Footer- und Logo-Links keinen Befund mehr (12 von 21 EU-Portalen, axe bestaetigte keinen); der Link im Absatz bleibt ein Befund.prefers-reduced-motion(2.3.3, AAA) meldet nicht mehr jedetransition/animation, sondern nur Bewegung — Animationen, deren@keyframestransform/translate/scale/rotate, Lage oder margin aendern, und Transitions solcher Eigenschaften (allnur, wenn eine:hover/:focus-Regel sie setzt) —, die kein Block@media (prefers-reduced-motion: reduce)fuer denselben oder den Universalselektor abschaltet; Regeln unterno-preferencezaehlen nicht. Farb- und Deckkraft-Uebergaenge zaehlen nie. Die Meldung nennt die Belege und die Stufe AAA; AAA war bereits weder rechtliches Signal noch hoch BFSG-relevant, ein Test sichert das jetzt ab. Neue Detection-Corpus-Faellelink_in_text_block_context,reduced_motion_colour_only,reduced_motion_transform,reduced_motion_override. Gezaehlt wird nur Bewegung, deren Selektor ein dargestelltes Element der Seite trifft (bundesregierung.de: Keyframes eines nie eingebundenen Player-Spinners). -
title-only und doppelte Beschreibung als eigene Best-Practice-Regeln (#711, #713): Das
title-Attribut als einzige Beschreibung eines interaktiven Elements stand alsa11y.hover.content_visibilityunter 1.4.13, obwohl der Browser-Tooltip dort ausgenommen ist; es ist jetzttitle-only-description→a11y.title_only_description.weak. Ein Element, dessen Name und Beschreibung identisch sind, stand alsa11y.interactive_name.missingim Bericht, obwohl es einen Namen hat; es ist jetztdescription-duplicates-name→a11y.description_duplicates_name.redundant. Beide sindbest-practice, an 4.1.2 verankert (titleist nach accname eine gueltige Namensquelle, 2.5.3 gilt nur bei sichtbarer Beschriftung), habenbfsg_relevance: lowund nie ein Rechtsflag —is_convention_rulefuehrt dafuer eine Liste von Best-Practice-Kennungen neben dendisplay/*-Regeln. 1.4.13 meldet nur noch verwaisterole="tooltip"-Elemente,interactive_name.missingnur noch Elemente ohne aussagekraeftigen Namen. Neuer Detection-Corpus-Fallname_description_best_practice,forms_and_miscum einen verwaisten Tooltip ergaenzt. -
a11y-rules 0.13.5 (#709): Der Sprunglink wird auch hinter den Links eines Cookie-Banners erkannt, wenn er auf den Anfang des Hauptinhalts zeigt (bund.de, casoon/barrierlab#26). Ist ein modaler Dialog offen (etwa ein Consent-Dialog, der
<main>peraria-hiddenausblendet), istlandmarks/main-missingein Pruefhinweis „gemessen hinter einem offenen Dialog” statt eines Verstosses (#709: fuenf von 21 EU-Portalen), auch bei einem Dialog ohnearia-modal(administracion.gob.es). Ebenso wirdbypass(„keine Ueberschriften”) hinter einem offenen Dialog zum Pruefhinweis. Referenzband gov.ie neu bewertet (80–98): die bis 1.7.1 gezaehlten zehn Linkfarben-Befunde waren freistehende Listenlinks, Fehlalarme nach #710. -
Zielgroesse misst nur sichtbare Ziele im fertigen Layout (#705, #706):
target-size-minimum(2.5.8) undtarget-size(2.5.5) zaehlen ein Element nur noch als Ziel, wenn es sichtbar und anklickbar ist: nichtcheckVisibility()-verborgen (mitopacityProperty,visibilityProperty; bewusst ohnecontentVisibilityAuto, damit Abschnitte ausserhalb des Viewports gemessen bleiben), nicht in[inert], ohnepointer-events: none— wie schon beim gleichwertigen Link. Das gilt fuer das gemessene Ziel und fuer die Nachbarn der Abstandspruefung; Links in einem geschlossenen<details>(Chrome behaelt ihre Box, verbirgt sie percontent-visibility: hidden) machten auf geographia.eu drei Links im Footer und in einer Liste zum Befund. OhnecheckVisibilitybleibt es beim bisherigen Groessenfilter. Ausserdem wartet jeder Viewport-Durchgang nach der Stabilitaetspruefung auf laufende endliche CSS-Animationen und -Transitions (document.getAnimations(), unendliche werden ignoriert), begrenzt durch ein zweites--stability-budget-ms; eine Animation, deren Restzeit das Budget uebersteigt, wird gar nicht erst abgewartet. Ein Ziel, das danach selbst oder ueber einen Vorfahren noch animiert, meldet die Regel als nicht gemessen (untested) statt mit seiner Zwischengroesse: Das Logo-Intro auf geographia.eu/atmosphere/ stand als 3×39 px im Bericht, fertig misst es 147×39 px. Neue Detection-Corpus-Faelletarget_size_hidden_neighboursundtarget_size_animation; gegen den Stand vor dem Fix schlagena#beside-detailsunda#slowfehl. Geprueft mitcargo test, Clippy und dem Detection-Corpus (Chrome). -
Darstellungsregeln entscheiden kein Rechtsurteil mehr (#704):
display/text-hiddenmeldet ein per CSS,hiddenoderaria-hiddenverstecktes[data-viz-text]nicht mehr als Verstoss, wenn ein gerendertes, erreichbares Element derselbenfigure[data-viz](oder die figure selbst) es peraria-describedby/aria-detailsreferenziert: accname 1.2 berechnet die Beschreibung auch aus verstecktem, direkt referenziertem Inhalt (ARIA15 genuegt 1.1.1). Stattdessen ein Pruefhinweis mit Schwere niedrig („nur als Beschreibung, visually-hidden empfohlen”).inertbleibt ein Verstoss. Alledisplay/*-Regeln sind Best Practice der Darstellungs-Konvention: Sie setzenbfsg_relevancehoechstens auflowund zaehlen nicht mehr alslegal_flags(Seiten- und Batch-Urteil,passedim JSON); ein gemeinsamesNormalizedFinding::is_legal_flagersetzt die gleichlautenden Filter. Bewusst nur fuerdisplay/*(taxonomy::is_convention_rule), nicht generisch ueber dasbest-practice-Tag —region,link-as-buttonundredundant-rolebehalten ihre Einordnung.display/toggle-missingsagt jetzt „kein mit[data-display-toggle]markierter Umschalter gefunden” statt zu behaupten, man koenne nicht umschalten. Belegt am Fall geographia.eu (Detection-Corpusdisplay_modes_described_text). -
Ausschluesse gelten auch fuer die Screenreader-Schicht (#703): Knoten in
--exclude-selector/[data-audit-exclude]-Teilbaeumen (#645) fallen jetzt auch ausscreen_reader.issuesund damit ausbfsg_complianceund dem daraus abgeleitetenrisk.legal_flags(#484). Verortet wird ueber die Backend-Knoten-ID des AX-Knotens, wie bei den Baum-Regeln (ExclusionScope::excludes_ax_node). Ein Befund faellt nur weg, wenn alle seine Knoten ausgeschlossen sind; sonst bleibt er mit den uebrigen Knoten. Befunde ohne Knoten (seitenweit) bleiben immer. Gezaehlt inexclusions.excluded_screen_reader_issues(auch im Batch-Aggregat und im PDF-Hinweis); das PDF, das die Befunde in der Laufsprache neu ableitet, laesst dieselben Knoten weg. Belegt an barrierlab.eu/tasks/ticket/(Detection-Corpusaudit_exclude_screen_reader, der dafuerscreen_reader_bfsg_verdictpruefen kann). -
1.7.1, 2026-09-30: Erster Schritt des Regelumzugs nach barrierlab (casoon/barrierlab#13): 21 Kennungen kommen aus
a11y-rules0.13.2, die abgeloesten eigenen Regeln sind geloescht (#690, B0). Neue Befundezoom/viewport-missing(Gewichtung folgt in #702) undimages/alt-suspicious; die geteilten Regeln pruefen per CSS Verstecktes nicht mehr. Referenzlauf (reference_sites_test) und Detection-Corpus gruen. Einzelheiten im Eintrag darunter. -
Geteilte Kennungen statt eigener Regeln (#690, B0): Weitere Befunde laufen ueber den geteilten Bestand aus
a11y-rules0.13.2 (SHARED_RULESinsrc/wcag/shared.rs), die abgeloesten eigenen Regeln sind geloescht, sodass kein Befund doppelt im Bericht steht. Kennungen:meta-viewport→zoom/viewport-locked;document-title(fehlend/leer) →document/title-missing/document/title-empty, der nichtssagende Titel bleibt alsdocument-title;aria-valid-attr→aria/reference-missing;aria-required-attr→aria/required-attribute-missing;aria-hidden-focusundfocus-order-semantics→keyboard/hidden-focusable(bisher derselbe Fall zweimal, unter 4.1.2 und 2.4.3);landmark-one-mainundlandmark-main-present→landmarks/main-missing(bisher zweimal);landmark-no-duplicate-main→landmarks/main-duplicate;landmark-banner-present,landmark-navigation-present,landmark-contentinfo-present→landmarks/*-missing, jetzt als Pruefhinweis statt Verstoss;svg-img-altundimage-altan<svg>→svg/name-missing;image-altan<img>→images/alt-missing(image-altbleibt fuerrole="img"an anderen Elementen und die Icon-Heuristik); Link und Button ohne Namen auscontrol-missing-label→links/name-missing/buttons/name-missing; die Sprunglink-Erkennung ausbypass→keyboard/skip-link-missing(Pruefhinweis, erkennt den Sprunglink am Ziel statt an einer Wortliste).bypassmeldet nur noch eine Seite ganz ohne Ueberschriften; die Sammelmeldung „weder Sprunglink noch main” entfaellt. Neu:zoom/viewport-missing(kein viewport-Meta-Tag, 1.4.4) undimages/alt-suspicious(Dateiname, Fuellwort oder ein, zwei Zeichen als Alt-Text; Pruefhinweis). Die geteilten Regeln laufen jetzt mit berechnetemdisplay/visibilityaus einem DOMSnapshot (run_full_inueberCdpDocument::rendered) und pruefen per CSS Verstecktes damit nicht mehr; scheitert der Snapshot, laufen sie ohne Stile. Ein Element ohne Gegenstueck im AX-Baum gilt als ignoriert (Inhalt eines geschlossenen<details>: geographia.eu bekam sonst 22links/name-missing).skip-linkauslandmark_granularentfaellt, der Fall istkeyboard/skip-link-missing. User-Agent-Shadow-Roots (Datumsfelder) kommen nicht mehr in den geteilten DOM. Taxonomie, Erklaerungen und Detection-Corpus sind nachgezogen. Geprueft mit Detection-Corpus (Chrome) und einem Vorher/Nachher-Lauf gegen 1.7.0 auf acht Live-Seiten; jede Abweichung ist erklaert (Umbenennung, entfallene Doppelmeldung, per CSS Verstecktes, oder neu und berechtigt), zwei Fehlalarme dabei ina11y-rules0.13.2 behoben. -
Score-Kalibrierung:
table_missing_captionist eine saubere Seite, 2026-09-30: Das Fixture stand im Band „genau ein Level-A-High”; dieser Befund war der Fehlalarm aus #659/#674 (implizites<tbody>unter<table><tr>verdeckte die Zeilen vor der Pflicht-Kind-Pruefung). Seit #680 traegt die Seite nur noch den Caption-Pruefhinweis und liegt mit 95 im Band ≥ 95; der Tag-Lauf von v1.7.0 war daran gescheitert. -
1.7.0, 2026-09-30: Neue Funktionen: Techniker-Modus
--technician(JSON je Seite plusindex.jsonundfindings.jsonl, Pfadfilter--include-path/--exclude-path, Plan 67), Darstellungsmodi--display calm|text|visual|all(#653), Ausschluss absichtlicher Beispiele ueberdata-audit-exclude/--exclude-selector, sichtbar im Bericht (#645), und vollstaendig englische PDF-Berichte mit--lang en. Dazu die Fehlalarm-Korrekturen aus #638-#659 und #673/#674 sowie der Umbau aus Plan 66 (WP0-WP8, Ausgabe per Golden-Harness unveraendert). Crate- Metadaten:documentationzeigt auf https://casoon.github.io/auditmysite/,homepageauf https://auditmysite.casoon.de/. Einzelheiten in den Eintraegen darunter. -
Refactoring API-Oberflaeche (Plan 66 WP8), 2026-09-29: Reiner Umbau ohne Aenderung an Ausgabe oder oeffentlicher Bibliotheks-API. Die 199 Stellen, die der Lint
unreachable_pubmeldet (158 in der Bibliothek, 41 im Binary), sind nach Compiler-Vorschlag aufpub(super)(140) bzw.pub(crate)(59) verengt — fuer Bibliotheksnutzer waren sie ohnehin unsichtbar; erreichbarepub-Items bleiben unangetastet (Semver/Studio, 1.7).lib.rsundmain.rssetzen jetzt#![warn(unreachable_pub)]. Die 100 Vergleichei18n.locale() == "en"laufen ueber den vorhandenen Helferoutput::localized::is_english; dafuer istoutput::localizednicht mehr an das Featurepdfgebunden (nurpickbleibt es). Verifiziert: Golden-Harness vor/nach identisch, Clippy mit beiden Feature-Sets. -
Refactoring
audit/pipeline.rs(Plan 66 WP7), 2026-09-29: Reine Verschiebung ohne Aenderung an Ablauf oder Ausgabe.src/audit/pipeline.rsist jetzt das Verzeichnissrc/audit/pipeline/:mod.rsbehaeltPipelineConfig,run_single_audit,audit_pagemit beiden Viewport-Durchlaeufen, die Regellaeufe und die Artefakt-Persistenz;assembly.rsnimmt die synchrone Nachbearbeitung auf (WCAG-Zusammenfuehrung, Viewport-Scores,aggregate_report, Modul-Laeufe, Audit-Qualitaet);throttled.rsdie gedrosselten Performance-Durchlaeufe samtrecover_after_throttled_failureund der Uebernahme des LhMobile-Werts. Inaudit_pagesind nur die wortgleichen Doppelungen herausgezogen (die dreiprepare_*_collection-Aufrufe je Durchlauf, der Screenshot-Match); Reihenfolge von Ausschluessen (#645), Darstellungsmodi (#653) und Journeys unveraendert, ein gemeinsamerrun_viewport_passbewusst nicht. Oeffentliche Pfade (auditmysite::audit::{audit_page, run_single_audit, PipelineConfig}) bleiben. Verifiziert: Golden-Harness vor/nach identisch,integration_testunddetection_corpus_test(--ignored) gegen echtes Chrome. -
Refactoring: Modul-Details und Page Health als Verzeichnismodule (Plan 66 WP6), 2026-09-29: Reiner Umbau ohne Verhaltensaenderung.
output/builder/single/module_details.rs(2.618 Zeilen) ist jetzt ein Verzeichnismodul mit einer Datei je Modul, gespiegelt anoutput/pdf/detail_modules/:performance.rs,seo.rs,platform.rs(Security, Mobile),html_conform.rs,commerce.rs,dark_mode.rs,design_quality.rs,ai_transparency.rs,experience.rs(UX, Journey); die Orchestrierung (build_module_details_from_normalized) bleibt inmodule_details.rs. Die Inline-Berechnungen imSeoPresentation-Literal sind benannte Funktionen (heading_summary,social_summary,technical_summary_rows,tracking_summary_rows,build_robots_presentation,build_image_efficiency_presentation,technical_issue_rows).seo/page_health.rs(3.128 Zeilen) behaelt Typen, Textfunktionen,analyze_page_healthund die URL-Analyse; neu sindpage_health/dom.rs(DOM-Inspektion, lokale HTML-Validierung),page_health/probes.rs(HTTP-Proben) undpage_health/issues.rs(collect_issues). Alle oeffentlichen Pfade bleiben (seo::page_health::collect_issuesperpub use). Die Umbenennungrun_w3c_html_validation→run_local_html_validationwar bereits mit WP9 erledigt. Verifiziert mit dem Golden-Harness (WP0) gegenmain:diff -rleer. -
Darstellungsmodi (
data-display-Konvention), 2026-09-29 (#653): Neues Modulsrc/display/und neue Option--display calm|text|visual|all. Ohne Option bleibt alles wie bisher (Voreinstellung der Seite). Mit einem Modus schreibt ein vor der Navigation injiziertes Skript (Page.addScriptToEvaluateOnNewDocument) die Wahl nachlocalStorage.display;calmundtextemulieren zusaetzlichprefers-reduced-motion: reduce(Emulation.setEmulatedMedia), damit auch Seiten ohne Konvention ihre reduzierte Variante zeigen. WeilsetEmulatedMediadie ganze Feature-Liste ersetzt, reicht die Dark-Mode-Analyse (Druck, Forced Colors, Dunkel-Kontrast) die Grund-Features jetzt durch (analyze_dark_mode_with_base_media) — sonst haette sie die reduzierte Bewegung fuer den Rest des Audits geloescht. JSON:audit_scope.display_mode(site_default|visual|calm|text) und je Seitepages[].display_modes(angebotene Modi, dargestellter Modus,set_before_body, Umschalter, Visualisierungen je Art, reduzierte Bewegung); PDF: Zeilen „Darstellungsmodus”/„Darstellungsmodi der Seite” im Methodik-Anhang, im Batch auf dem Deckblatt.--display allprueft jeden Modus als eigenen Lauf mit eigenem Bericht (Suffix-calm/-text/-visual), nie ein gemischter Score;visualnur fuer Seiten mitfigure[data-viz="3d|interactive"]aus demcalm-Lauf, mit doppeltem Seiten-Timeout. Befunde werden nicht als „in allen Modi” markiert — dafuer muessten die Laeufe zusammengefuehrt werden.Neue Regelgruppe
display/*(Best Practice, keine WCAG-Anforderung, verankert an 2.2.2 bzw. 1.1.1; eigene Taxonomie-Eintraege und Erklaerungen):display/toggle-missing,display/init-missing(ueber einen Beobachter, der beim Einfuegen von<body>festhaelt, obdata-displayschon gesetzt war),display/text-media-visible,display/text-not-visible,display/text-hidden. Die Textmodus-Pruefungen richten sich nach dem tatsaechlich dargestellten Modus. Umzugskandidaten fuer barrierlab: die statischen Haelften vontoggle-missingundtext-hidden(Attribut-Teil =viz/text-hiddendes Entwurfs). Korpus-Fixturesdisplay_modes_conforming/display_modes_violations, Integrationstestdisplay_text_sets_stored_choice_and_reduced_motion(localStorage undmatchMediavor dem ersten Skript und nach dem Audit).Live gegen geographia.eu:
--sitemap .../sitemap-index.xml --display calm -m 10266 s, 10/10 Seiten, keine Pool-Timeouts (die ersten 10 URLs sind Textseiten ohne Visualisierung). Zehn Seiten mit Visualisierungen per URL-Liste:calm1151 s, 7/10, 0 Pool-Timeouts, aber 3 Seiten mitinteractivelaufen in das 120-s-Audit-Limit;text293 s, 10/10, 0 Timeouts. Befunde: auf allen Seitendisplay/toggle-missing(kein[data-display-toggle]ausgeliefert); incalm/visualdisplay/text-hidden, weil.viz-text { display: none }die Textschicht ausserhalb vontextauch fuer Screenreader entfernt (im Browser bestaetigt); intextdisplay/text-not-visibleauf Seiten ohne[data-viz-text](Startseite 4, Erklaerungs-Labor 10). -
Englischer Bericht: Fuellwoerter als Themen, irrefuehrender Validator-Titel, 2026-09-29: Befund aus einem Scan der englischen Typst-Ausgaben des Golden-Sets (17 Seiten). (1) Der Batch-Bericht fuehrte unter „Dominant content topics” den Eintrag „nicht” („Present on 2 audited pages”). Ursache:
seo/topics.rsfiltert Seitentitel, Beschreibung, Ueberschriften und Textauszug gegentopic-stopwordsaus den FTL-Dateien, und diese Listen enthielten nur einen Teil der Funktionswoerter — „nicht” (barrierlab.eu, casoon.de), „weitere” (berlin.de), „dein”/„dich” (hornbach.de, otto.de) und „neue” (volkswagen.de) rutschten als Seitenthemen durch. Beide Listen um gaengige Funktionswoerter (Negation, Hilfs- und Modalverben, Pronomen, Konjunktionen, Praepositionen) mit mindestens vier Zeichen ergaenzt; kuerzere filtert der Code ohnehin. Echte Themenwoerter bleiben. (2) Der Callout zur HTML-Validierung hiess „W3C HTML validator”/„W3C HTML Validator”, obwohl die Pruefung lokal mit html5ever laeuft und nie den W3C-Dienst aufruft. Jetzt „HTML validation (local, html5ever)” bzw. „HTML-Validierung (lokal, html5ever)“; der FTL-Schluessel heisstpdf-ph-html-validator-title. (3) Die deutschen Zeilen der HTML-Validierungstabelle im englischen PDF waren bereits mit #671 behoben. (4) Die Terminal-Ausgabe zeigte mit--lang en„Certificate: SEHR GUT”. Das Zertifikat im JSON bleibt bewusst das kanonische deutsche Token (#449: Schluessel fuer Badge/Farbe, per Enum im Studio-Vertragsschema festgelegt, von report-lint geprueft und von rankinglab ausgewertet); nur die Anzeige wird lokalisiert. (5) Der Validator-Callout verglich den lokalisierten Status mit „Fehlgeschlagen”; im englischen PDF („Failed”) wurde eine fehlgeschlagene Validierung deshalb als Info statt als Warnung gezeigt. Die Praesentation traegt jetzt den kanonischen Status (executed/failed/skipped), der Callout entscheidet darueber.certificate_label_localizedliegt jetzt inregistrystatt im PDF-Cover, damit auch das Terminal es nutzt: mit--lang ensteht dort „EXCELLENT”. Ebenso zeigte die Zeile „Certificate” im Kasten „Audit scope” des englischen Batch-Berichts das deutsche Token („AUSBAUFÄHIG”); sie nutzt jetzt dieselbe Funktion („INADEQUATE”).Aendert die Ausgabe: Seitenthemen (
topic_terms,top_terms, Themen-Ueberschneidungen) im JSON und in beiden PDF-Sprachen, wo eine Seite ein Funktionswort unter ihren fuenf Themen hatte; Titel des Validator-Callouts in beiden Sprachen; Zertifikatszeile im englischen Terminal und im englischen Batch-Kasten „Audit scope”. Das JSON-Feldcertificatebleibt unveraendert. Verifiziert mit dem Golden-Harness gegenmain: Unterschiede nur an diesen Stellen, deutsches Typst bis auf Validator-Titel und Themenliste wortgleich. Unit-Tests gegen Funktionswoerter als Themen und gegen Umlaute in den englischen Zertifikats-Labels. -
document-titledoppelt gezaehlt, 2026-09-29: Ein fehlender oder leerer Seitentitel stand zweimal im Bericht, einmal aus der AX-Pruefungcheck_page_titled(Knotendocument, ohne Selektor) und einmal aus der DOM-Pruefungcheck_page_titled_with_page(Selektorhead). Massgeblich ist jetzt allein die DOM-Pruefung: Den Namen des Wurzelknotens fuellt Chrome bei fehlendem Titel mit der URL, die AX-Pruefung konnte diesen Fall deshalb selbst nicht entscheiden und verwies schon bisher auf die DOM-Pruefung. Alles, was sie als Verstoss meldete (leer, fehlend, generischer Titel), meldet die DOM-Pruefung ebenfalls; die AX-Pruefung ist samt Tests entfernt (oeffentliche Funktionwcag::rules::check_page_titledentfaellt).empty_titlepinnt 1 Vorkommen, der Integrationstest aufparity_gaps.htmlebenfalls. -
Tabellen: ignorierte und noch nicht gerenderte Zeilen (#654, #659), 2026-09-29: (1)
aria-rolesmeldete auf og-vanilla.casoon.dev (/accessibility,table.keys) „role ‘table’ is missing required child roles: row, rowgroup” fuer eine Tabelle mittbody > tr. Chrome markiert ein schlichtes<tbody>als ignoriert, die Zeilen haengen darunter; die Pruefung der geforderten Kind-Rollen sah nur direkte Kinder. Sie schaut jetzt durch ignorierte Knoten hindurch (gleiches Muster wie #638). Eine Tabelle ohne Zeile (nurcaption) bleibt gemeldet. (2)th-has-data-cellsmeldete die Kopfzellen desrole="grid"-Rasters in einer Shadow-Root (/columns,/accessibility), obwohl jede Spalte Daten hat. Im Nachstellen per CDP lag die Aufnahme vor dem Eintreffen der Daten: Das Raster haelt 40 Pool-Zeilen mitdisplay: noneim DOM, bis der Worker liefert (rund 300-500 ms nachload); Chrome fuehrt sie als ignoriert mitnotRendered. Die Stabilitaetswartung sieht Mutationen in Shadow-Roots nicht und meldete die Seite nach 255 ms stabil. Stehen in einer Tabelle ohne Datenzellen solche nicht gerenderten Zeilen an Zeilenposition (direkt unter der Tabelle, unter einer rowgroup oder einem ignorierten<tbody>), gilt das Ergebnis jetzt als unbestimmt (incomplete) statt als Verstoss. Versteckte Inhalte innerhalb einer Kopfzelle zaehlen nicht; eine Tabelle nur mit Kopfzeile bleibt gemeldet.Neue Korpus-Fixtures
table_required_rows_tbodyundtable_grid_rows_unrendered(Shadow-DOM- Raster mit wartenden bzw. gerenderten Zeilen, Tabelle nur mit Kopfzeile als Verstoss) und Unit-Tests in beiden Regeln. -
Technician-Modus: Einzelseiten-JSON zum Beheben statt Bericht zum Lesen, 2026-09-29 (Plan 67): Neu fuer Leute, die Befunde abarbeiten:
--sitemap URL --technician -o DIR/(ebenso mit--url-file/--crawl) schreibt pro Seite den regulaeren Einzelbericht als JSON und daneben zwei flache Dateien.index.jsonfuehrt jede versuchte URL in Eingabereihenfolge — Dateiname odernull, Statusok/blocked/failedmit Grund, Gesamt- und Barrierefreiheits-Score, Zahl der Befunde und Vorkommen,audit_quality. Gesperrte (Bot-Wall,AccessBlocked) und fehlgeschlagene Seiten stehen nur dort, ohne Platzhalterdatei; dafuer traegtBatchErrorjetztblocked_reason(nicht serialisiert, Batch-JSON unveraendert).findings.jsonlenthaelt eine Zeile pro Vorkommen ueber alle Seiten (url,sourcewcag/journey/seo/html_conform,rule_id,wcag_criterion,level,severity,selector,location,message,fix_suggestion,viewport_tags), vollstaendig — die Seitendatei kappt ihre Beispiel-Vorkommen pro Befund auf fuenf. Beide Dateien sind kanonisch Englisch (#406), Schemas unterdocs/technician-index.schema.jsonunddocs/technician-finding.schema.json. Jeder Lauf mit--per-page-reports -f jsonschreibt sie.--techniciansteht fuer--per-page-reports -f json --no-screen-reader-report --seo --html-conform: Barrierefreiheit (inkl. Tastatur-Journeys), HTML-Konformitaet und SEO, keine gedrosselten Performance-Durchlaeufe, kein Mobile/Security/Tech-Stack. Jede Seitendatei nennt diesen Umfang inexecution.scope/module_runs. Ausdruecklich gesetzte Flags gewinnen:-fersetzt das Format,--full/--performance/--mobile/--securityschalten ihre Module wie gewohnt zu. Einzeln nutzbar sind die neuen Flags auch:--include-path/--exclude-path(wiederholbar, Glob auf den dekodierten URL-Pfad:*/?innerhalb eines Segments,**segmentuebergreifend,/**/auch als einzelnes/; wirkt vor-m;sample.selectionwird dannpath_filter),--no-screen-reader-report(kein*-screen-reader-audit.json, auch im Einzelmodus) und--html-conform(HTML-Konformitaet ohne--full).Nicht in
findings.jsonl:accessibility_assessments(Hinweise/manuelle Pruefpunkte, keine Verstoesse). Die abgeleiteten Indikator-Module (UX, Journey, Commerce, Content Visibility, AI Visibility, Source Quality, Dark Mode) laufen weiter, weil sie ancheck_seobzw. fest anPipelineConfighaengen; sie abzuschalten braeuchte neuePipelineConfig-Felder. -
3.2.2 On Input: nur echte Kontextwechsel sind Verstoesse, 2026-09-29 (#657): Auf og-vanilla.casoon.dev (
/filtering,/sorting,/localization) meldetea11y.on_input.riskSelects fuer Filter-Preset, Sortierung und Sprache als „may trigger navigation”, nur weil ihr Name „filter”/„sort”/„language” enthielt und die Seite keinen Absende-Button hat. Die Selects aktualisieren aber nur das Grid daneben — eine Inhaltsaenderung ist kein Kontextwechsel. Jetzt ist nur noch ein Verstoss, was der Inline-onchange-Handler (oder die globale Funktion, die er direkt aufruft) sichtbar tut: Navigation (location…), Formular absenden (.submit(/.requestSubmit(), neues Fenster (window.open() oder Fokusverschiebung (.focus(). Ein Handler, dessen Wirkung sich nicht ablesen laesst, und ein Name, der nur nach Navigation klingt (language, country, region, navigate, redirect, go to), werden zur Pruefwarnung statt zum Verstoss; „sort” und „filter” fallen als Hinweis ganz weg. Die Einordnung liegt jetzt in Rust (evaluate) und ist unit-getestet. Neue Korpus-Fixtureon_input_context_change(besteht: Filter ohne Handler; Pruefung: inhaltsaendernder Handler, Sprach-Select; Verstoss: Handler navigiert ueber aufgerufene Funktion, Handler sendet Formular). -
1.3.5 Identify Input Purpose: „Name” einer Sache ist kein Personenname, 2026-09-29 (#658): Auf og-vanilla.casoon.dev/saved-views galt
#view-name(Label „Name”, Name einer gespeicherten Grid-Ansicht) als Feld fuer den Namen der Nutzerin, weil das Label „name” als Teilstring enthielt. „name” wird jetzt wortweise eingeordnet (Trennung an Satzzeichen und camelCase): ein vorangestelltes Wort, das keine Person und kein technisches Id-Praefix ist („view name”, „project name”, „company name”), ein folgendes „of/for/der/des/für/von” („Name der Ansicht”) und Komposita wie „Dateiname”/„Filename” schliessen das Feld aus; „Name”, „Your name”, „Full name”, „Vorname”, „Nachname”, „Ihr Name” bleiben Treffer. Ein Sach-Qualifier inidodername-Attribut schliesst ein blosses „Name”-Label aus, sofern das andere Attribut nicht die Person nennt. Die Erkennungswoerter sind Englisch und Deutsch zusammengefuehrt (wie beimedia_alternative), unabhaengig von der Ausgabesprache. Neue Korpus-Fixtureinput_purpose_name. -
Absichtlich kaputte Beispiele vom Audit ausnehmen, sichtbar im Bericht, 2026-09-29 (#645): Seiten, die Barrierefreiheit lehren, zeigen bewusst fehlerhafte Beispiele (barrierlab.eu,
/tasks/ticket/: unbeschriftete Felder, eindivals Button). auditmysite meldete sie als eigene Verstoesse der Seite. Neu, nach dem Vorbild von axe-coresexclude:--exclude-selector <CSS>(wiederholbar),[audit] exclude_selectorsinauditmysite.toml, und das immer beachtete Attribut[data-audit-exclude]. Ausgenommen wird nie eine Seite und nie eine Regel, sondern nur Befunde, deren Element in einem ausgeschlossenen Teilbaum liegt — die Seite selbst wird vollstaendig geprueft, seitenweite Befunde bleiben immer.Umsetzung (
src/audit/exclusion.rs): je Viewport-Durchgang zaehlt einRuntime.evaluatedie Treffer jedes Selektors (ungueltige Selektoren werden erkannt); nur bei Treffern holtDOM.getDocument+DOM.querySelectorAlldie Backend-Node-IDs aller Knoten der getroffenen Teilbaeume (inkl. Shadow Roots, Frame- und Template-Inhalt). Gefiltert wird je Regel inrun_rules, bevor ihr Ausfuehrungsvermerk gezaehlt wird, und vor Anreicherung und Element-Screenshots: Befunde aus dem AX-Baum ueber die Backend-ID ihres AX-Knotens, Befunde der geteilten DOM-Regeln ueber die neu mitgefuehrte Backend-ID (Violation::backend_node_id, nur im Speicher; gesetzt im Adapterwcag/shared.rs, am Regelcode von barrierlab aendert sich nichts), JavaScript-Seitenregeln, Kontrast und Journey-Befunde ueber ihren Selektor — ausgenommen nur, wenn er mindestens ein Element trifft und jedes davon im ausgeschlossenen Teilbaum liegt; ein mehrdeutiger oder nicht parsebarer Selektor behaelt den Befund. Muster-Befunde laufen durch denselben Filter.Sichtbarkeit: Das JSON fuehrt je Seite
pages[].exclusions(angewandte Selektoren mit Trefferzahl — auch 0 und ungueltig —, ausgeschlossene Vorkommen und bestaetigte Verstoesse gesamt und je Regel, ausgeschlossene Journey-Befunde; je Regel der groessere Wert der beiden Viewports), der Batch zusaetzlichsummary.exclusionsals Summe. Das PDF nennt Selektoren und Zahlen im Methodik-Teil, der Batch im Audit-Rahmen des Deckblatts (de/en ueber Fluent). Ein Standardlauf ohnedata-audit-excludeauf der Seite bleibt im PDF still, das JSON listet den eingebauten Selektor mit 0 Treffern. Cache-Format 19; die Selektoren gehen in die Audit-Signatur ein.Obergrenzen der JavaScript-Regeln: Viele Seitenregeln melden hoechstens N Fundstellen (10 bei
click-events-have-key-events, 20 beilink-as-button, 250 bei den ARIA-Regeln, 5 bei Zielgroesse, verdeckt fokussierten Elementen, Sprachwechseln u. a.). Ohne Gegenmassnahme haetten Beispiel-Treffer die Obergrenze aufgebraucht und echte Treffer dahinter still verschwinden lassen — genau das, was die Funktion ausschliessen soll. Deshalb legt der Ausschluss-Schritt je Durchgangwindow.__amsIsExcluded(el)in die Seite (die getroffenen Wurzeln in einemWeakSetim Closure, kein DOM-Attribut, keine fuer andere Regeln sichtbare Aenderung). Der gemeinsame BausteinCSS_SELECTOR_JS, den jede lokalisierende Regel einbindet, bringt__amsIsExcludedEl,__amsPushund__amsRealmit: Echte und ausgeschlossene Treffer werden getrennt gedeckelt, die Schleifengrenze liest nur die echten. Ausgeschlossene Treffer kommen weiter zurueck und werden wie alle anderen gefiltert und gezaehlt (je Regel hoechstens bis zur selben Obergrenze). Umgestellt:aria_allowed_attr,aria_hidden_focus(deren Gesamtzahl zaehlt Ausgeschlossene nicht mehr mit),aria_relationships,aria_roles(2),aria_required_attr,image_input_rules,on_focus,on_input,server_side_image_map,table_extended,widget_rules,click_handlers,content_on_hover,fake_navigation_link,redundant_role,use_of_color,language_of_parts,meaningful_sequence,target_size_minimum/_enhanced(auch die Kandidatenlisten),focus_not_obscured_minimum/_enhanced(Fokus-Kandidaten, die 60 geprueften Bedienelemente, die 5 Befunde),pause_stop_hide(Widgets; die seitenweite Animations-Meldung ignoriert ausgeschlossene Elemente ganz) undaccessible_authentication(Obergrenze von Rust ins Skript verlegt).identify_purposemeldet einen Feldnamen statt eines CSS-Pfads — das Skript markiert ausgeschlossene Felder selbst (Violation::in_excluded_subtree, nur im Speicher), die Obergrenze 5 gilt getrennt. Kontrast: Text in ausgeschlossenen Teilbaeumen belegt keinen der 60 Plaetze fuer die Pixelprobe mehr.rule_outcomes[].findingszaehlt jetzt, was im Bericht steht: Die Baum-Regeln filtern je Regel inwcag::check_all_excluding(neu;check_all_with_configbleibt als Huelle), die Seitenregeln, Kontrast und HTML-Inhaltsmodell vorpage_rule_outcome; bei den geteilten Regeln wird der ausa11y-rulesuebernommene Zaehler um die entfallenen Befunde derselben Kennung verringert.Bleibt offen: Regeln, deren gemeldeter „Selektor” kein CSS-Pfad ist und die ihr Element nicht selbst markieren, koennen ausgeschlossene Treffer nicht verlieren (bekannt und behoben:
identify_purpose). Obergrenzen, die nur Kandidaten fuer eine seitenweite Aussage deckeln (Overlay-Suche infocus_not_obscured_*, Felder je Formular inredundant_entry), sind unveraendert.Tests: Unit-Tests fuer Filter, Zaehlung und PDF-Text (EN ohne Umlaute), neue Korpus-Fixture
audit_exclude_specimen(je ein Fehler pro Ortungsweg — AX-Knoten, JS-Seitenregel, geteilte DOM-Regel — innerhalb von[data-audit-exclude]nicht gemeldet, derselbe Fehler ausserhalb gemeldet) und ein Integrationstest fuer--exclude-selectorsamt 0-Treffer- und ungueltigem Selektor. Dazu Korpus-Fixtureaudit_exclude_cap: 12/22/6 ausgeschlossene Treffer vor je einem echten (Obergrenzen 10/20/5) — der echte wird gemeldet, undexcluded_hits_do_not_spend_a_capped_rules_budgetprueft dasselbe samt Ausschluss-Zaehlung undrule_outcomes[].findings == 1. Gegenprobe: mit abgeschaltetem__amsIsExcludedElfaellt der Test (div#real-clickfehlt). -
4.1.2/3.3.2: aufklappbare Treegrid-Zeilen und native Wertfelder, 2026-09-29 (#655, #656): (1) Das Accordion-Muster hielt jedes Element mit
aria-expandedfuer einen Accordion-Ausloeser und meldete auf og-vanilla.casoon.dev/grouping jede Gruppenzeile desrole="treegrid"als „should be a button” (Medium, 12 Vorkommen).aria-expandedauf einer Zeile ingrid/treegridund auf einemtreeitemist der Zustand des eigenen zusammengesetzten Widgets, dessen Tastaturvertrag (Pfeiltasten, Enter) das Grid bzw. der Baum stellt; solche Knoten zaehlen nicht mehr als Ausloeser. Eine aufklappbare Zeile ausserhalb eines Grids wird weiter gemeldet.(2)
aria-required-attrprueftearia-valuenowueber eine AX-Eigenschaftvaluenow, die CDP nicht kennt — der aktuelle Wert steht imvaluedes Knotens, und Chrome erfindet einen (50 beim Slider, 0 beim Spinbutton), wenn der Autor keinen setzt. Die Pruefung schlug deshalb bei jedemslider/spinbutton/meter/fokussierbarenseparatoran, auch bei nativem<input type=range|number>und den internen Tag/Monat/Jahr-Feldern von<input type=date>(Critical; auf /filtering, /columns, /grouping, /theming 16 Vorkommen). Aus demselben Grund meldetecheck_slider_has_value(„Slider is missing accessible value”) jeden Slider: Der Extraktor liest nur Text-Werte, die Zahl des Sliders fiel weg. Diearia-valuenow-Pflicht laeuft jetzt als DOM-Regelcheck_value_now_with_page(wie schonaria-checked): sie liest das Attribut, durchlaeuft offene Shadow Roots, nimmt nativesrange/numberund<meter>aus und erreicht die User-Agent-Felder von Datumsfeldern gar nicht. Die Slider-Pruefung inwidget_rules.rsentfaellt; der eigenerole="slider"ohnearia-valuenowbleibt ein Verstoss.(3) Die 3.3.2-Heuristik „may require format instructions” uebergeht Knoten innerhalb der Chrome-Rollen
Date/DateTime/InputTime— deren Felder und Format stellt der Browser. Die Rollespinbuttonallein loest den Hinweis nicht mehr aus: 3.3.2 verlangt Anleitungen, wo die Eingabe einem Format folgen muss, das man nicht erschliessen kann; ein Spinbutton — natives<input type=number>wie eigenesrole="spinbutton"— haelt eine Zahl, die das Widget selbst begrenzt und mit den Pfeiltasten schrittweise aendert. Die Erkennung ueber die Beschriftung („Date”, „Postal code”) gilt weiter fuer jede Rolle.(4) Der AX-Extraktor las
valuenur als Zeichenkette; Chrome schickt den Wert von Slider, Spinbutton, Progressbar und Meter als Zahl, der damit verloren ging. Zahlen und Wahrheitswerte bleiben jetzt in Textform erhalten. Keine WCAG-Regel liestAXNode.valuemehr (die einzige,check_slider_has_value, ist oben entfallen); Folge hat es nur fuer die Screenreader-Linearisierung (a11y-perception): Wertelemente ohne Namen tragen jetzt ihren Wert und zaehlen in der Ansage-Wuesten-Messung als angesagter Inhalt, was sie beim Vorlesen auch sind.Live nachgeprueft mit dem Release-Build gegen og-vanilla.casoon.dev: keine Accordion-, keine
aria-valuenow-, keine Slider-Wert- und keine Spinbutton-Format-Befunde mehr. Neue Korpus-Fixturestreegrid_expandable_rows,value_widgets_native(bestehen),value_widgets_customundvalue_widgets_shadow(Verstoss);aria_and_widgetserwartet den eigenen Slider jetzt unteraria-required-attr.Nachzug derselben Fehlerklasse: Auch die uebrigen 1.3.5-Stichwoerter wurden als Teilstring gesucht — „Hotel” galt wegen „tel” als Telefonfeld, „Sorting” haette „ort” enthalten. Sie gelten jetzt nur als ganze Woerter (tel, zip, plz, fax, city, first, last …) oder als bekannte Kompositum-Anfaenge bzw. -Enden (
telefon…,postleit…,birth…,geburts…,…adresse,…address,…strasse); „E-Mail” zaehlt als ein Wort. Deutsche Felder wie „Telefonnummer”, „Postleitzahl”, „Straße”, „Passwort” werden damit erstmals erkannt. Ebenso bei 3.2.2: die seitenweite Suche nach einem Absende-Button fand „go” in „Google”, „Category” oder „Logo” und unterdrueckte dadurch die Pruefwarnung; die Button-Namen werden jetzt wortweise geprueft (submit, send, go, search, absenden, senden, suchen), die Entscheidung liegt in Rust und ist unit-getestet. Korpus:#stay(„Hotel”) besteht, „Telefonnummer”/„Postleitzahl” sind Verstoesse; die On-Input-Fixture hat Google/Category/Logo-Buttons, der Sprach-Select bleibt Pruefung. -
landmark-uniquedoppelt gezaehlt, 2026-09-29: Die Regel lief zweimal, einmal ueber den AX-Baum (check_landmark_unique) und einmal in der DOM-Ergaenzung (check_landmarks_with_page). Beide meldeten dieselben Elemente mit unterschiedlich gebildeten Selektoren, sodass nichts zusammengefuehrt wurde: Der Korpusfalllandmark_granularkam auf 22 statt 11 Vorkommen,parity_gaps.htmlauf 4 statt 2. Massgeblich ist jetzt allein die AX-Pruefung; sie arbeitet mit Chromes berechneten Rollen und Namen und erfasst jede Landmark, die die DOM-Naeherung sieht (dazu<section>mit Namen,form, und korrekte Rollen fuer verschachtelteheader/footer/aside). Dasselbe galt fuerlandmark-main-present: Eine fehlendemain-Landmark stand zweimal im Bericht (rootunddocument). Die DOM-Ergaenzung hatte dabei keine eigene Abdeckung: Faellt der AX-Baum aus, bricht der ganze Audit ab; bei Mini-Seiten mit hoechstens zwei AX-Knoten schweigt die AX-Pruefung absichtlich; und ein<main>in einem Shadow Root sah nur Chrome,querySelectorAllnicht, sodass die DOM-Pruefung dort faelschlich meldete. Die DOM-Ergaenzung (check_landmarks_with_page, Page-Regel1.3.1/landmark-dom) ist deshalb samt Rollenableitung entfernt. Der Korpus kann jetzt peroccurrencesdie genaue Anzahl pinnen:landmark_granular11, der neue Falllandmark_unique_one_per_element4 (1.6.0: 6),landmark_aside_named_duplicate2,missing_main_landmark1 (1.6.0: 2); der neue Falllandmark_main_in_shadow_rootist ein echtes Negativ.parity_gaps.htmlprueft die Anzahl im Integrationstest (1 fehlendemain, 2 doppelte Navigationen). -
Page Health: HTML-Validierung im JSON kanonisch Englisch (#406, Plan 66 WP9), 2026-09-29:
seo/page_health.rsschrieb deutschen Text ins kanonische JSON:html_validator_detail(„HTML5-Validierung lokal via html5ever”) sowiecheck/detailallerhtml_issues(„Doppelte IDs”, „3 (z.B. …)“, „HTML5-Parsing-Fehler”, „+37 weitere”, „Leere Ueberschriften” usw.).reconcile_image_alt_countinaudit/pipeline.rsverglich sogar gegen den deutschen String „Bilder ohne alt-Attribut”. Jetzt nach dem #406-Muster:HtmlValidationIssuetraegt einkind(HtmlValidationKind) und die Rohwerte insamples(Beispiel-IDs bzw. die ersten Parser-Fehler);html_validation_check_text/html_validation_detail_text/html_validator_executed_textsind die einzige Textquelle, die Analyse ruft sie miten=true, der PDF-Builder (builder/single/serp.rs) mit der Lauf-Sprache. Vergleiche laufen ueberkind. Die privaterun_w3c_html_validationheisst jetztrun_local_html_validation(sie ruft nie den W3C-Validator auf).Aendert die JSON-Ausgabe:
html_issues[].check/detailundhtml_validator_detailsind englisch, jeder Eintrag hat neukind, Eintraege mit Rohwerten zusaetzlichsamples. Das deutsche PDF bleibt wortgleich; das englische PDF zeigt dort jetzt englischen Text statt des bisherigen deutschen. Cache-Eintraege derselben Version mit altem deutschem Text werden beim Einlesen aufkind+sampleszurueckgefuehrt. Verifiziert mit dem Golden-Harness (WP0) gegenmain: Unterschiede nur in diesen Feldern im JSON und im englischen Typst, deutsches Typst und Batch-Ausgaben unveraendert; Guard-Test gegen Umlaute/deutsche Woerter in der EN-Ausgabe. -
2.5.8/2.5.5 Target Size: Ausnahme „Equivalent” fuer Links, 2026-09-29 (#652): Beide Kriterien nehmen ein zu kleines Ziel aus, wenn dieselbe Funktion ueber ein anderes Bedienelement auf derselben Seite erreichbar ist, das die Groesse erfuellt. Das fehlte: auf geographia.eu (
/atmosphere/de/kapitel/hebel/) galt die 39×10 px kleine Absenderzeile des Modul-Logos (a.logo-main,href="/de/") als Verstoss, obwohl der Footer-Link „Alle Sphaeren im Ueberblick” (/de/#modules, 141×161 px) dieselbe Startseite oeffnet. Jetzt besteht ein zu kleiner Link, wenn ein anderer Link mit demselben Ziel mindestens 24×24 (2.5.5: 44×44) misst, gerendert und sichtbar ist (checkVisibilityinkl.visibility/opacity, nicht links/oberhalb der Seite verschoben), Pointer-Events hat und nicht in eineminert- oderaria-hidden="true"-Teilbaum liegt.Das Ziel ist die aufgeloeste absolute URL. Fuehrt der Link in ein anderes Dokument, zaehlt das Fragment nicht —
/de/und/de/#modulesoeffnen dieselbe Seite, das Fragment setzt nur die Scrollposition. Fuehrt er in die aktuelle Seite, zaehlt es: dort ist das Springen an die Stelle die ganze Funktion,#aund#bsind verschieden.href="",href="#"undjavascript:zaehlen nie — sie nennen kein Ziel. Ein ebenfalls zu kleiner Link ist kein Aequivalent, auch wenn er selbst ueber die Abstands-Ausnahme besteht (bewusst konservativ). Buttons bleiben aussen vor, ihre Funktion steht nicht im Markup. Der BausteinhasEquivalentLinkliegt inTARGET_HELPERS_JS, beide Regeln nutzen ihn. Die Regel fuehrt keine Pass-Liste, daher wird das ausgleichende Element nicht vermerkt.Neue Korpus-Fixture
target_size_equivalent(besteht: kleiner Link mit grossem Aequivalent, auch mit anderem Fragment in ein anderes Dokument; Verstoss: nur versteckte/inerte/unsichtbare/ verschobene Aequivalente, nur zu kleine, anderes Fragment in die eigene Seite,href="#", ohne Aequivalent) und ein Integrationstest, der beide Regeln direkt aufruft. -
Batch: Pool-Wartezeit und still fehlende Seiten (#651), 2026-09-29: Ein Batch ueber geographia.eu (
-c 2 -t 120, WebGL-Seiten) verlor 46 von 312 Seiten mit „Browser pool timeout: no page available after 60 seconds”. Nachgestellt mit 1.6.0 auf 8 Kapitelseiten: 3 von 8 fehlten. Zwei Ursachen: (1)BrowserPool::acquirezaehlt den Slot, bevor die neue Seite angelegt ist; schlugnew_pagefehl (Chrome unter der WebGL-Last, CDP-Anfrage laeuft ab), gingen Semaphor-Genehmigung und Slot dauerhaft verloren — der Pool hatte fuer den Rest des Laufs eine Seite weniger. Beides wird jetzt zurueckgegeben, und das Anlegen der Seite ist auf--timeoutbegrenzt: Im Nachstellen unter Last bliebnew_pageohne Antwort stehen, was 1.6.0 nur ueber das 480-s-Versuchsbudget abfing. (2) Die Wartezeit auf eine freie Seite war fest 60 s, eine Seite in Arbeit darf abermax(timeout, 30) * 4Sekunden brauchen (480 s bei-t 120); die wartende Seite gab auf, obwohl mit ihr nichts war, und verbrauchte dabei auch ihren Wiederholungsversuch. Die Wartezeit folgt jetzt--timeout(Anlegen der Seite plus Versuchsbudget plus 15 s fuer das Zuruecksetzen, 615 s bei-t 120), liegt ausserhalb des Versuchsbudgets der Seite und verbraucht keinen Versuch. Seiten, die trotzdem keine Browser-Seite bekommen, werden nach der parallelen Phase einzeln wiederholt, bevor der Bericht entsteht. Was dann noch scheitert, steht wie bisher untererrors; neu sagt der Bericht, wie viele URLs die Scores abdecken: JSONsummary.attempted_url_count(additiv, nebenurl_count), PDF eine Zeile „Bewertungsbasis” im Audit-Rahmen und ein Hinweis mit den nicht auditierten URLs im Statusabschnitt (de/en), Terminal eine Scope-Zeile. Die Score-Berechnung ist unveraendert. Verifiziert mit Unit-Tests (Ableitung der Wartezeit, nur Pool-Timeouts werden zurueckgestellt, Bericht in JSON/PDF/Terminal) und dem Chrome-Integrationstestbatch_retries_pool_timeouts_serially(ein Slot, zwei Worker, 1 s Wartezeit: alle drei Seiten auditiert, jede genau einmal gemeldet). Live mit denselben 8 Kapitelseiten und-c 2 -t 120: 1.6.0 verlor 3 (zwei Pool-Timeouts nach dem Slot-Verlust, ein Audit-Timeout nach 480 s), der Fix auditierte 8 von 8 — allerdings bei geringerer Rechnerlast, der Fehlerpfad selbst wurde dabei nicht beruehrt. -
Skip-Link und
role="list", 2026-09-29 (#642, #644): (1)region(1.3.1) meldete den Skip-Link<a href="#main">Aller au contenu</a>auf allen franzoesischen Seiten von barrierlab.eu, auf den englischen, deutschen und spanischen nicht. Die Ausnahme hing am Linktext: Die Pruefung aufhref="#…"verglich mit#, Chrome liefert alsurlaber die aufgeloeste Adresse (https://barrierlab.eu/fr/#main), also griff nur die Phrasenliste, und der fehlte das Franzoesische. Jetzt zaehlt das Verhalten wie bei axe-coresisSkipLink: ein Link auf ein Fragment derselben Seite (Adresse ohne Fragment gleich derurldes Wurzelknotens), der vor dem ersten gewoehnlichen Link steht. Die Phrasenliste ist entfallen; sie nahm auch jeden Link mit „springen” im Text aus. (2)redundant-role(4.1.2) meldeterole="list"an<ul>/<ol>mitlist-style: none(1.142 Vorkommen auf barrierlab.eu). WebKit/VoiceOver verwirft bei solchen Listen die Listensemantik, die Rolle stellt sie wieder her. Bei berechnetemlist-style-type: nonebleibt sie jetzt unerwaehnt; alle anderen redundanten Rollen werden weiter gemeldet. Geprueft mit Unit-Tests, zwei neuen Faellen im Detection-Korpus (skip_link_language,redundant_role_list_style, je mit weiter gemeldetem Gegenbeispiel) und Live-Laeufen gegen barrierlab.eu/fr/ und /en/. -
Kontrast bei Verlaufstext, #640, 2026-09-29:
a11y.contrast.weakmeldete fuer die Logo-Links auf geographia.eu (background-clip: textmit Verlauf,color: transparent) einen Verstoss mit 1,00:1. Das war keine Messung: Die transparente Textfarbe wurde auf den Hintergrund gerechnet und ergab den Hintergrund selbst. Die Stilerfassung setzt jetztforeground-uncertain, wenn das Element oder ein Vorfahrbackground-clip: text(auch-webkit-) traegt odercolor/-webkit-text-fill-color(nahezu) transparent ist. Solche Texte gehen als Pruefhinweis (Warnung) ohnecontrast_ratio-Beleg in den Bericht und werden nicht per Pixelabtastung aufgeloest, weil diese die CSS-Textfarbe braucht. Zudem gilt eine gesetzte, deckende-webkit-text-fill-colorjetzt als Textfarbe (Kontrastrechnung und Pixelabtastung), denn Browser malen die Glyphen damit, nicht mitcolor(Korpusfalltext_fill_color_contrast: helle Fuellfarbe bei dunklemcolorist Verstoss, umgekehrt bestanden). Belegt mit Unit-Tests und dem Korpusfallgradient_text_contrast(Verlaufstext Hinweis, grauer Fliesstext weiter Verstoss); Live-Lauf gegen geographia.eu/atmosphere/de/kapitel/hebel/: beide 1,00:1-Verstoesse sind Hinweise, weitere Kontrastbefunde gab es auf der Seite weder vorher noch nachher (Barrierefreiheit 75 → 86, gesamt 74 → 79). -
Heuristische Formularbefunde, 2026-09-29 (#643): Zwei Vermutungsregeln meldeten auf barrierlab.eu korrekt gebaute Formulare. (1) „Input may require format instructions” (3.3.2,
label) liess eine Accessible Description nur gelten, wenn sie ein Formatwort wie „Format:” oder „z.B.” enthielt; das Datumsfeld des Musterformulars mitaria-describedby(„Day of travel”) fiel durch. Ausserdem traf der Begriff „pass” (Reisepass) per Teilstring das Label „Was ist passiert?” der Kontakt-Textarea. Jetzt zaehlt jede Beschreibung als Anleitung, Begriffe unter fuenf Zeichen (pass,tel,date,zip,plz,ssn) zaehlen nur als ganzes Wort, und der Befund ist ein Pruefhinweis (as_warning) statt eines Verstosses, weil der Formatbedarf nur aus dem Labeltext geraten ist. (2) „Grouped form controls may be missing a fieldset/legend” (1.3.1,form-field-group) verlangte fuer jede Checkbox eine Gruppe, sobald die Seite irgendwo zwei Radio-/Checkbox-Elemente hatte — auf /tasks/bill/ und /tasks/appointment/ traf das die einzelne Checkbox neben einer korrekt gruppierten Radio-Umschaltung. Zusammengehoerig sind Checkboxen ueber ein gemeinsamesnameim selben Formular; das traegt der AX-Baum nicht. Die Pruefung fuer Checkboxen laeuft deshalb als DOM-Seitenregel (check_checkbox_group_with_page): erst ab zwei sichtbaren Checkboxen gleichen Namens wird einfieldset/role=group-Vorfahr verlangt. Radios bleiben unveraendert im Baum (ein Radio braucht immer eine Gruppe). Neue Korpusfaelleform_heuristics_cleanundform_heuristics_flagged. -
--per-page-reports: eine Datei je Seite, 2026-09-29: Die Dateinamen der Einzelberichte kamen nur aus dem Host (casoon-de-<datum>-single-report.json); jede Seite einer Website ueberschrieb die vorige, aus einer Sitemap mit drei Seiten blieb eine Datei. Der Name enthaelt jetzt Host und Pfad (casoon-de-arbeitsweise-…), bei Query-Strings einen kurzen stabilen Hash. Gilt fuer alle Formate; Einzelaudits ohne--per-page-reportsbehalten ihren Namen. -
Zwei Fehlalarme auf geographia.eu (#638, #639), 2026-09-29: Auf geographia.eu/atmosphere/de/kapitel/hebel/ meldete 1.6.0 zwei Verstoesse gegen 1.3.1, die keine sind. (1)
th-has-data-cells(8 Vorkommen, hoch): Chrome legt<thead>alsrowgroupin den AX-Baum, markiert das schlichte<tbody>aber als ignoriert; Zeilen und Zellen haengen trotzdem darunter. Die Regel liess ignorierte Knoten samt Teilbaum aus, fand so keine einzige Datenzelle und meldete jede Spaltenueberschrift. Leere Zellen undaria-hidden-Kinder spielten keine Rolle. Jetzt steigt die Suche durch ignorierte Knoten hindurch, zaehlt sie aber selbst nicht. (2)landmark-unique(2 Vorkommen, mittel): Die DOM-Ergaenzung zu den Landmark-Regeln leitete die Rolle aus dem Tag ab und machte jedes<header>zumbanner, auch das Kapitel-<header>in<main>. Nach HTML-AAM (und in Chromes AX-Baum) sind<header>/<footer>inarticle,aside,main,nav,sectionoder den entsprechenden Rollen generisch; die DOM-Ergaenzung folgt dem jetzt. Dasselbe galt fuer<aside>: Innerhalb vonarticle,aside,navodersection(bzw. den Rollenarticle,complementary,navigation) ist es nur mit zugaenglichem Namencomplementary, sonst generisch;role=regionzaehlt dabei nicht, ein unbenanntes<section>schon (so rechnet Chrome). Die DOM-Ergaenzung beruecksichtigt dafuer auchtitleals Namen. Belegt am Live-Lauf: beide Befunde weg, die uebrigen vier unveraendert, Barrierefreiheitswert 75 auf 85. Neue Korpusfaelletable_headers_tbody_ignoredundlandmark_header_in_main,landmark_aside_scoping(echte Negative) undlandmark_aside_named_duplicate(echtes Positiv);table_headers_no_dataundlandmark_granularsichern die bisherigen echten Positive. -
Englische PDFs ohne deutsche Tabellenbeschriftungen, 2026-09-29: Mit
--lang enstanden in den Tabellen zu Sicherheits-Headern („Vorhanden”/„Fehlt”), SSL („Gueltiges Zertifikat”, „Laeuft ab in … Tage”, „Chain-Laenge”), Touch-Targets („Zu klein”, „Zu eng beieinander”) und Schrift („Kleinste Schrift”, „Lesbarer Text”) deutsche Beschriftungen, gefunden an einem englischen casoon.de-Bericht. Die Beschriftungen folgen jetzt der Laufsprache; ein Guard-Test mit den Moduldaten dieses Laufs prueft die englische Ausgabe auf deutsche Begriffe. -
Cache-Rundreise und deterministische Reihenfolge, 2026-09-29: Beim Aufbau des Golden-Render-Harness (Plan 66) fielen zwei Fehler auf. (1)
GraphEntity.propertieswurde mitskip_serializing_ifweggelassen, trug aber keindefault: Ein gecachterreport.jsonmit einer Entitaet ohne Eigenschaften liess sich nicht mehr lesen („missing fieldproperties“), und die CLI auditierte stillschweigend neu. Betroffen waren 13 der 17 eingefrorenen Cache-Eintraege. Dieselbe Luecke hattenBatchReport.errors, die vier Listen inSitemapDiagnosticsund die drei inCrawlDiagnostics; alle tragen jetztdefault. Neue Rundreise-Tests serialisieren und lesen einen vollstaendigenAuditReportund einenBatchReportmit leeren Listen. (2) Zwei Laeufe ueber dieselbe Eingabe konnten verschiedene Berichte liefern, weil Ergebnisse ausHashMaps ohne vollstaendige Sortierung kamen: Bei Gleichstand konnten sich Reihenfolge und (nach dem Kuerzen auf 10) Inhalt vontop_recurring_rulesaendern; Schema-Konflikte im Batch, die interaktiven Kategorien samt PDF-Tabelle „Befunde nach Kategorie”, die dominante Regel der Zusammenfassung, die Hauptrolle im Massnahmenplan, Template-Cluster, doppelte Inhalte, Minifizierungs-Abweichungen und die Sitemap-HTTP-Befunde (Abschlussreihenfolge der Anfragen) hatten dieselbe Luecke; die Strafpunkte des Accessibility-Scores wurden inHashMap-Reihenfolge summiert. Jede Sortierung hat jetzt einen letzten eindeutigen Schluessel (Regel-ID, Entitaets-ID, Kategorie, Selektor, URL), die Strafpunkte werden in Regel-ID-Reihenfolge summiert. Tests fixieren die Reihenfolge bei Gleichstand. Geprueft: drei Golden-Render-Laeufe ueber alle 17 Seiten,diff -rleer. -
1.6.0, 2026-09-29: Sammelversion der Stabilitaets- und Genauigkeitsarbeit seit 1.5.1 (Plaene 58-65, Sperrseiten, Abgleich mit dem rankinglab-Korpus; Einzelheiten in den Eintraegen darunter). Abhaengigkeiten auf die dabei veroeffentlichten Stande gehoben:
a11y-rules,a11y-dom,a11y-report,accname0.12.2 (Listenregeln sehen durch<slot>,<ul role=listbox>ist keine Liste) undhtml-conform0.3.1 (bringt die vnu-naeheren Pruefungen aus 0.3.0 mit:imgohnealt, interaktiver Inhalt ina, weitere Parserfehler; dazunameneben RDFa-propertyan<meta>). HTML-Konformitaetswerte koennen dadurch sinken. -
Befunde der report-lint-Pruefung ueber 120 rankinglab-Berichte, 2026-09-29: 8 Berichte fielen durch. (1) Das Quellenqualitaets-Signal „Bedienelemente benannt” zaehlte alle rohen 4.1.2- und 1.1.1-Verstoesse, die Lint-Pruefung alle normalisierten; seit Plan 61 tragen
aria-hidden-focusundaria-prohibited-attr4.1.2 nur normalisiert, und beide Zaehlungen widersprachen sich (5 Berichte). Inhaltlich waren beide zu breit: Doppelte IDs oder verbotene ARIA-Attribute sind kein fehlender Name.taxonomy::is_missing_name_or_roleist jetzt die eine Definition fuer Blocker, Signal und Lint; das Signal bildet rohe Verstoesse wie die Normalisierung auf die Taxonomie ab. (2)labels::check_linkpruefte nebenbei generische Linktexte (nur englisch, ohne die Kontext-Ausnahme aus #569) und meldete dieselben Links wielink_purposeals zweite Zeile derselben Taxonomie-Regel (deutschebahn.com 7 + 5 Vorkommen,violated_rule_counteins zu klein, 3 Berichte). Die Nebenpruefung ist entfernt. -
Abbrueche im rankinglab-Lauf, 2026-09-29: Von 122 rankinglab-Seiten scheiterten 7; vier davon liefen mit 1.3.1 problemlos. (1) Einzelaudits mit vollem Chrome unter macOS blieben mit der CDP-Fokus-Emulation aus Plan 62 zeitweise ganz stehen: www.deutschebahn.com hing in 3 von 6 Laeufen (jeder CDP-Befehl lief 30 s in den Timeout, der Audit ueberschritt rankinglabs 12-Minuten-Grenze), ohne Emulation liefen 6 von 6 in 103-128 s mit gleichen Ergebnissen. Die Emulation war fuer verborgene Tabs paralleler Batch-Seiten gedacht und gilt jetzt nur dort (
BrowserOptions::focus_emulation, gesetzt bei--concurrency> 1). (2) Scheitert ein gedrosselter Performance-Aufruf, laedt die Seite unter der CPU-Drosselung weiter; die Aufraeumbefehle warteten dann je 30 s. Jetzt loest die Pipeline zuerst die CPU-Drosselung, stoppt das Laden, jeder Schritt mit 5 s, und laesst die uebrigen Profile aus, wenn der Tab nicht antwortet. (3) Die Vorpruefung vor dem Browserstart brach bei einem HEAD-Timeout ab: www.regierung-mv.de beantwortet HEAD gar nicht (GET in 0,3 s), www.ing.de braucht 9-10 s je Antwort bei fest 10 s Wartezeit. Jetzt entscheidet ein GET, und gewartet wird so lange wie-t. Keine Fehler bei uns: www.unito.it und www.douglas.de sperren (403), www.tu-berlin.de drosselte voruebergehend (429). -
Berichtsgenauigkeit nach dem Abgleich mit dem rankinglab-Korpus, 2026-09-29: Zwei Berichtskritiken (gov.uk, sachsen-anhalt.de) gegen 1.3.1 fanden Fehler, die Urteil und Zahlen verfaelschten; jeder ist am Code bzw. an den Daten nachgeprueft. (1) Die Zusammenfuehrung von Desktop und Mobile paarte jeden Mobile-Befund mit dem ersten Desktop-Befund gleichen Selektors, auch wenn der schon vergeben war: 18 leere Listen je Ansicht wurden 35 Vorkommen. Jetzt eins zu eins. (2)
role="status"/alertohne Namen galt als 4.1.2-Verstoss; ARIA verlangt fuer diese Rollen keinen Namen. Auf gov.uk trugen zwei leere Status-Regionen das „nicht bestanden”. (3) Die Combobox-Regel suchte die Optionsliste nur im Teilbaum;aria-controlsund der geschlossene Zustand zaehlen jetzt (APG-Autocomplete auf gov.uk). (4) „Bedienelemente ohne Namen” zaehlte alle 4.1.2-Befunde ab mittel, auch doppelte IDs und unbenannte Dialoge; jetzt nur fehlender Name/fehlende Rolle und 2.1.1. Der Satz „0 kritische und 0 hohe Befunde, davon 3 …” nennt die Blocker nicht mehr als Teilmenge. (5) Sicherheitskarte und Modultabelle folgen der korrigierten Wortstufe (Plan 33) auch in Farbe und Tabelle; die Quellenqualitaets-Karten nutzen eine Skala statt drei; der Aufwand einer Massnahme steigt mit der Komplexitaet des Befunds („Geringe Komplexitaet” neben „53 Vorkommen deuten auf ein Template-Problem”). (6) Kein doppelter Punkt im Anhang, deutsche Bereichsnamen bei Touch-Targets, und die Zeile unter den Zaehlern trennt Stellen zur Handpruefung von den nur manuell pruefbaren Kriterien. (7) JSON bleibt kanonisch Englisch (#406): Seit #633 standen die Texte der geteilten Regeln in der Laufsprache im JSON. Die Regeln laufen jetzt englisch; ein deutscher Lauf prueft zusaetzlich auf Deutsch und legt die Texte inWcagResults::localized_textsab (nicht serialisiert), die PDF-Builder (Einzel und Batch) setzen sie ein. (8) Der DOM fuer die geteilten Regeln ist der flache Baum: Per Slot zugewiesene Light-DOM-Knoten haengen unter ihrem<slot>, nicht zugewiesene erscheinen nicht. Bisher hingen sie neben dem Shadow-Inhalt, und jede<ul><slot>einer Web-Komponente stand leer da (sachsen-anhalt.de). (9) Beim Beenden meldeteBrowserManager::close„Failed to close page: Session with given id not found” fuer die schon geschlossene Audit-Seite (5 von 15 Einzelaudits); das ist jetzt eine Debug-Meldung. Nachkontrolle an neu erzeugten Berichten: (10) Blocker zaehlen Elemente, nicht Regeltreffer (drei Namensregeln am selben Menue-Element auf sachsen-anhalt.de waren „3 Bedienelemente”), mit Einzahl im Satz. (11) Die Checkliste „Alle Verstoesse” zeigt die geteilten Texte ebenfalls deutsch. (12) Quellenqualitaet: Kartenfarben nach denselben Schwellen wie die Wortstufe (40 statt 60 fuer rot), und „alle Signale in Ordnung” nur, wenn jede Dimension gut ist (gov.uk: 80+ gesamt, Substanz 70). (13) Eine Landmark-Massnahme mitaria-labelbekommt nicht mehr den Titel „Interaktive Elemente (Buttons, Links) verstaendlich benennen”. -
Sperrseiten werden nicht mehr bewertet, 2026-09-29: Beim Abgleich mit dem rankinglab-Korpus fiel auf, dass www.douglas.de automatisierten Browsern seit mindestens 1.3.1 eine Akamai-Seite „Access Denied” mit HTTP 403 und rund 100 Knoten liefert; sie wurde mit 75 bewertet und als vollstaendig gemeldet. Mit der headless-shell lieferte www.hornbach.de eine Fastly-„Client Challenge” mit CAPTCHA (355 statt 12.942 Knoten, 85 statt 56). Neues Modul
audit::access_block: HTTP 401/403/407/429, eindeutige Challenge-Titel der Anbieter oder Challenge-Ressourcen auf einer duennen Seite (< 300 Elemente; Cloudflare laedt/cdn-cgi/challenge-platform/auch in regulaere Seiten) lassen das Audit der URL mitAccessBlockedscheitern: Einzelaudit mit Exit-Code 3 und Grund, im Batch untererrors, ohne Wiederholung. Die Pruefung laeuft direkt nach dem ersten Aufruf; Challenge-Seiten wechseln oft sofort ihr Dokument, daher bis zu drei Versuche und ein zweiter Blick auf den Dokumentstatus spaeter in der Pipeline. Geprueft: douglas.de 3 von 3 gesperrt, hornbach.de mit Chrome normal und mit headless-shell gesperrt, Batch ueber 15 Vergleichsseiten ohne Fehlalarm. -
Batch unter macOS mit vollem Browser seriell gegen haengende Journeys, 2026-09-29 (Plan 65): Im Referenz-Batch (gov.uk, bundesregierung.de, dm.de, berlin.de,
--full, drei Worker) liefen mit dem System-Chrome 154 in 4 von 4 Laeufen Journeys ins 10-s-Budget, zweimal blieb der Browser ganz stehen (> 5 bzw. > 10 min). Alle offenen Seiten liefen in derselben Sekunde in den Timeout, unabhaengig von der Journey. Eine Stichprobe des Browser-Prozesses zeigte den Hauptthread in der AppKit-Tastaturverarbeitung (performKeyEquivalent→SLSObscureCursor, wartend auf den WindowServer): Auch--headless=newfaehrt unter macOS die Ereignisschleife. Gegenproben: nur die Journeys seitenuebergreifend serialisieren half nicht (1-10 Timeouts); ohne Fokus-Emulation kein Totalhaenger, aber wieder Tab-Walk-Timeouts in verborgenen Tabs (Plan 62); ein Worker: 2 von 2 sauber (100-120 s statt 38 s); Einzelaudits 6 von 6 sauber;chrome-headless-shell: 5 von 5 sauber, aber www.hornbach.de lieferte ihr eine Bot-Sperrseite (355 statt 12.942 Knoten, bewertet mit 85 statt 56). Aenderung: Ohne--concurrencylaufen Batches mit Journeys unter macOS mit vollem Browser seitenweise; der Laufstart unddoctorsagen es. Nachher: 2 Referenz-Batches ohne-cin 97-111 s, einer sauber, im anderen ein einzelner Modal-Test auf dm.de ueber 10 s (alspartialausgewiesen, kein Haenger). Die Browserauswahl fand eine perbrowser install --headless-shellinstallierte Shell bisher gar nicht; sie ist jetzt Rueckfall nach Chrome for Testing, wenn kein System-Browser da ist. Messungen inreports/p65/. -
JSON und PDF zeigen dieselben Befunde in derselben Reihenfolge, 2026-09-28 (Plaene 58-64): Stabilitaetspruefung vor 1.6.0 an gov.uk, bundesregierung.de, dm.de und casoon.de, je zwei Laeufe. Scores waren stabil, die Berichte nicht deckungsgleich. (1)
top_actionsund die „5 wichtigsten Massnahmen” reihten nach zwei Logiken; jetzt giltaudit::prioritization::action_orderfuer JSON und PDF: Schwere, darin BFSG-Pflicht (WCAG A/AA) zuerst, dann Hebel, Vorkommen undrule_id. Das PDF uebernimmt die Reihenfolge der Normalisierung, auch in der Befundmatrix;top_actionsfuehrtrule_id. (2) Das PDF nahm den Titel aus der Erklaerung, die fuer Regeln ohne eigene auf den Kriteriumstext zurueckfaellt, und fasste gleiche Titel zusammen: vier 4.1.2-Regeln wurden „Fehlende Name/Rolle” mit summierter Anzahl. Titel kommen jetzt aus der Taxonomie; zusammengefasst wird nur dieselbe Stelle aus WCAG und SEO, ohne zu addieren. (3) Gleichstaende und die Seitenreihenfolge im Batch hingen von der Eingabe- bzw. Fertigstellungsreihenfolge ab. (4)wcag_criterionenthielt beiaria-prohibited-attr,aria-hidden-focusundframe-testeddie Regel-Kennung statt 4.1.2. (5) Im Batch waren alle Seiten ausser einer verborgene Tabs:requestAnimationFramefeuerte nie,document.hasFocus()war false, der Tab-Walk lief auf 7 von 10 casoon.de-Seiten in den Timeout und Fokus-Pruefungen massen anders als im Einzelaudit. Jede Seite laeuft jetzt mit CDP-Fokus-Emulation, sichtbar und fokussiert (casoon.de-Batch 84 s -> rund 50 s, keine Timeouts). Ein eigenes Fenster je Seite leistete dasselbe, liess Chrome aber mit mehreren schweren Seiten gleichzeitig haengen (Referenz-Batch 215 s statt 57 s) und wurde verworfen.run_single_auditschliesst seine Seite. (6)report-lintpruefte das PDF-Zertifikat gegenoverall_scorestatt gegen das gespeicherte Zertifikat. (7) Das Batch-JSON listete intop_actionsdieselbe Regel je Seite einmal, das Batch-PDF fasste zusammen, sortierte aber eigen und nannte in „Wirkung” die Anzahl einer Seite; beide nutzen jetztaggregate_batch_findings(je Regel summiert, Schwere als Maximum, abgeleitete Felder aus der Gesamtzahl),top_actionsfuehrturl_count. Neue Tests: JSON-PDF-Paritaet fuer Einzel- und Batch-Bericht, Reihenfolge unabhaengig von der Eingabe, sichtbare und fokussierte Parallel-Seiten. -
a11y-*0.12: deutsche Befundtexte,ids/duplicateim Kern, 2026-09-28: Die geteilten Regeln laufen jetzt in der Laufsprache des Berichts (run_with_semantics_in,--lang deist die Vorgabe). Seit 0.11 kamen ihremessage-Texte englisch und standen so in deutschen PDF-Berichten. Der WCAG-2.2-Filter fuerids/duplicate(nur per IDREF referenzierte Duplikate, 4.1.2) liegt jetzt ina11y-rulesselbst und gilt damit auch fuer astro-post-audit und LiveAudit;IDREF_ATTRS,referenced_ids,duplicate_id_is_referencedund die Nachzaehlung am Vermerk sind entfallen. Die Duplikat-Tests inshared.rspruefen dasselbe Verhalten weiter, neu ist ein Test fuer deutsche Texte. Geprueft: 1749 Tests gruen, clippy und fmt sauber. -
Strukturierte Daten aus
web-checks0.4, 2026-09-28: Regeltabellen, JSON-LD-Normalisierung und Bewertung (schema_rules.rs, Normalisierungshaelfte vonschema.rs) liegen jetzt inweb_checks::structured_data, geteilt mit astro-post-audit (Plan barrierlab/02, Entscheidungen D1–D8). Hier bleiben CDP-Erhebung, Inhaltsabgleich (schema_parity), Mikrodaten/RDFa und die Berichtstexte (feature_label,status_text,manual_review_text). Aenderungen: Breadcrumb-Name darf ausitem.namekommen (tagesschau.de: drei falsche Pflichtbefunde weniger); FAQPage verlangtacceptedAnswerje Frage; NewsArticle empfiehltpublisher, WebSitepotentialAction(Qualitaet von WebSite jetzt ueber 5 statt 4 Angaben);RULESET_VERSION2026-09-28. JSON-Form:rule_assessments[].manual_reviewenthaelt Kennungen (merchant_listing_context, …) statt englischer Saetze (#406-Muster; das PDF leitet die Texte ab). Strukturmeldungen im JSON nennen den Block, Kennungen (jsonld_*) unveraendert. -
Meta-Laengen und OpenGraph aus
web-checks0.3, 2026-09-28: Titel- und Description-Laenge kamen an vier Stellen (meta.rs,serp.rs, zweimalprofile.rs) mit denselben Grenzen 30–60 / 120–160 und zaehlten Bytes: Jeder Umlaut zaehlte doppelt, ein deutscher Titel mit 60 Zeichen konnte „zu lang” sein. Jetztweb_checks::meta(Zeichen, Leerraum wie im Browser zusammengefasst), geteilt mit astro-post-audit. OpenGraph/Twitter: Tag mit leeremcontentgilt als fehlend (vorher vorhanden), eine Gruppe nur aus leeren Tags als nicht vorhanden; Vollstaendigkeit ueberweb_checks::social. Neu inmeta_issues: ungueltigertwitter:card-Wert und relativesog:image(je Medium), bisher nur in astro-post-audit geprueft. web-checks von 0.1 auf 0.3 (robots unveraendert). -
accname-Differential mit berechnetem Stil, a11y-core 0.11.3, 2026-09-28:
accname-diffrechnet die eigene Seite jetzt mitaccname::name_rendered:display/visibilitykommen aus einem CDP-DOMSnapshot(fetch_dom_document_with_layout,LayoutStyles,RenderedCdpDocument), ebenso die Leerraum-Textknoten, dieDOM.getDocumentauslaesst. Die geteilten Regeln laufen unveraendert ueberfetch_dom_document. Korpus (35 Seiten): ein echtes Abweichungsmuster statt sechs (2 Vorkommen, per Skript eingefuegter Text auf bundesregierung.de) — „EU-Arktis”, „Rechenpower”, „abholen*“, eindisplay:none- und ein<br>-Fall stimmen jetzt mit Chrome ueberein. -
a11y-core 0.11.2, 2026-09-28 (Plan 52):
accname,a11y-dom,a11y-report,a11y-rulesvon 0.11.0 auf 0.11.2. 0.11.1 (Leerzeichen zwischen Inline-Elementen nach Tag) ist uebersprungen und zurueckgezogen: Im accname-Korpus fiel die Namensgleichheit von 97,7 % auf 92,9 %, weil per CSS zu Bloecken gemachte<span>zusammengeklebt wurden. 0.11.2 nimmt das zurueck und haelt<dt>(Rolleterm) als namenlos fest. Korpus mit 0.11.2: 97,9 %, 6 echte Abweichungen wie zuvor. Die richtige Loesung (Trenner aus dem berechnetendisplay) liegt in barrierlab. -
Meta-Refresh-Zwischenseiten im JSON, 2026-09-28 (Plan 47): Dass
navigateeine Zwischenseite (kurzer<meta http-equiv="refresh">) verfolgt oder eine echte Weiterleitung per Refresh abgebrochen hat, stand bisher nur im Log. Jetzt fuehrtaccessibility.execution.navigation.meta_refreshje Viewport-Durchgangoutcome(interstitial_followed|redirect_not_followed) undhops; ohne Refresh fehlt das Feld. -
Leere Ueberschrift mit eigenem Taxonomie-Eintrag, 2026-09-28 (Plan 56):
headings/emptyfiel aufa11y.headings.missingzurueck — „Fehlende Ueberschriftenstruktur”, Schwere High, pauschal 20 Punkte Abzug. Eine leere Ueberschrift ist ein einzelner Defekt, keine fehlende Gliederung. Jetzta11y.heading_empty.invalid(Medium, 2,5–8 Punkte logarithmisch wie die anderen Ueberschriftenregeln, Bereich Ueberschriften) mit eigener Erklaerung DE/EN. -
Fehlende Navigations- und Fussbereich-Landmark mit eigener Kennung, 2026-09-27 (Plan 56): Die beiden Pruefungen in
landmarks.rsvergaben keinerule_id, fielen damit auf ihr Kriterium zurueck und landeten im 1.3.1-Sammelbucketa11y.structure.missing(„Missing semantic structure”, allgemeine Erklaerung mit Tabellen-Beispiel). Jetztlandmark-navigation-presentundlandmark-contentinfo-presentmit eigenen Taxonomie-Eintraegen (a11y.landmark_navigation.missing,a11y.landmark_contentinfo.missing, Bereich „Landmarks”), Erklaerung DE/EN und Korpus-Erwartungen (missing_main_landmark,landmarks_and_lists). Erkennung und Schwere unveraendert. -
Eigene Erklaerungen fuer die geteilten Kennungen, 2026-09-27 (Plan 56): Die geteilten Kennungen mit eigenem Taxonomie-Eintrag (Ueberschriftenebene, h1 fehlt/mehrfach, vier Listen-, drei Tabellenregeln, ungueltiger Sprachcode, positiver tabindex, begrenzter Zoom) hatten keinen eigenen Erklaerungstext.
resolve_explanationfiel auf den Text ihres Kriteriums zurueck: Eine leere Liste las sich im PDF und infix_guidancewie „Fehlende semantische Struktur” mit Tabellen-Beispiel, ein ungueltiger Sprachcode wie eine fehlende Sprachangabe. Jetzt hat jede einen Text (deutsch und englisch, mit Beispielcode), geschluesselt nach der Taxonomie-Kennung. Die beiden Texte fuerlistundmeta-viewport-largewaren unter toten Kennungen abgelegt und nie erreichbar; sie sind in die Eintraege fuera11y.list_structure.missingunda11y.viewport_zoom.restrictedaufgegangen (der Listentext sprach noch von leeren Listen, die inzwischen einen eigenen Eintrag haben). Ein Test haelt fest, dass keine geteilte Kennung mit eigenem Eintrag mehr auf den Kriteriumstext zurueckfaellt.headings/emptyhat keinen eigenen Taxonomie-Eintrag und bleibt beim Text zu 2.4.6. -
Tote Katalog-Eintraege entfernt, 2026-09-27 (Plan 56):
LEGACY_WCAG_MAPfuehrte nochlist,meta-viewport-largeundtable-duplicate-name— Kennungen, die keine Regel mehr vergibt (die ersten beiden sind an die geteilten Regelnlists/*bzw.zoom/viewport-scale-limitedgegangen, die dritte hat es als Pruefung nie gegeben). Ebensoa11y.table_structure.invalid, nur uebertable-duplicate-nameerreichbar, samt Bereichszuordnung und Erklaerung. Geprueft per Suche ueber Quelltext, Tests, Erklaerungen und PDF-Pfad. Die Eintraegea11y.list_structure.missingunda11y.viewport_zoom.restrictedbleiben, sie tragen die geteilten Kennungen. -
Eine Pruefung fuer die Gruppierung von Radio-Buttons und Checkboxen:
form-field-group, 2026-09-27 (Plan 56): Zwei eigene Pruefungen meldeten denselben Radio-Button ausserhalb einer Gruppe:form_rules::check_grouped_controlsunterform-field-multiple-labels(in axe-core heisst das „Feld mit mehreren Labels” — ein anderer Defekt) undinfo_relationshipsunterradio-group. Letztere pruefte nur den direkten Elternknoten und meldete damit zu Unrecht, sobald ein<label>zwischen Radio und<fieldset>lag. Jetzt gibt es eine Pruefung mit eigener Kennungform-field-group(a11y.form_field_group.missing): Ein Radio-Button braucht immer eine Gruppe als Vorfahren, eine Checkbox erst, wenn die Seite mehr als ein Radio-/Checkbox-Feld hat.check_info_relationshipsist damit leer und geloescht; die Laufkennungdefinition-list/radio-groupentfaellt,form-field-multiple-labelsheisst als Lauf- und Filterkennung jetztform-field-group(wer sie inauditmysite.tomlabgeschaltet hatte, muss den Namen nachziehen). Taxonomie-Eintraga11y.radio_group.missingentfernt, Erklaerung und Korpus-Erwartungen (forms_and_misc,landmarks_and_lists) umgestellt. Accessibility-Score:forms_and_misc71 → 72 (die beiden Radio-Buttons zaehlten doppelt); ueber alle Plan-56- Aenderungen dieses Tages ist das die einzige bewegte Fixture-Seite, axe-Vergleich Spearman −0,536 unveraendert. -
Tabellen- und Listenpruefung aus
info_relationshipsgeloescht, 2026-09-27 (Plan 56): Die beiden Teilpruefungen meldeten unter der Laufkennungdefinition-listund doppelten die geteilten Regeln. Verglichen: „Datentabelle ohne Kopfzellen” (AX-Rolletablemit Zellen, aber ohnecolumnheader/rowheaderunter Kindern und Enkeln) ist eine Teilmenge vontables/header-missing(jede<table>bzw.role=table|gridohne<th>/Kopfzellenrolle im ganzen Teilbaum); „Liste ohne Listeneintraege” (AX-Rollelist, nur fremde Kinder) eine Teilmenge vonlists/invalid-structure+lists/empty. Echte Duplikate, also nach demSHARED_RULES-Grundsatz entfernt, samt der Zellen-Pruefung, die nur Bestanden-Zaehler hochzog. Einzige Luecke:<menu>zaehlt fuer Chrome als Liste, fuer die geteilte Regel nicht. Mit den Pruefungen fallen der Taxonomie-Eintraga11y.definition_list.invalidund die Erklaerung, die von<dl>sprach, obwohl nie eine Beschreibungsliste gemeint war. Die Radio-Pruefung laeuft vorerst unter der Laufkennungradio-groupweiter. Der Korpus-Fallmisc_content_checkserwartet jetzt ausdruecklichtables/header-missingfuer seine Tabelle ohne Kopfzellen. -
valid-langgeloescht: der ungueltige Sprachcode kam doppelt, 2026-09-27 (Plan 56): Die eigene Regelvalid-lang(language_extended.rs) las dasselbe<html lang>wie die geteiltedocument/lang-invalidund meldete denselben Defekt ein zweites Mal. Nach dem Grundsatz vonSHARED_RULES(das eigene Gegenstueck faellt, sobald die geteilte Kennung uebernommen ist) ist die eigene Pruefung entfernt; die geteilte deckt sie ab (Primaerkennung 2-3 Buchstaben, dazu die Untertags, und auch ein leereslang).html-xml-lang-mismatchbleibt eigen. Mit ihr fallen der Taxonomie-Eintraga11y.language_valid.invalidund seine Zuordnungen; die Korpus-Erwartunginvalid_lang_codeerwartet jetztdocument/lang-invalid. -
accname-Differential: Namensquelle aus Chromes Detail,
image=img, 2026-09-27 (Plan 52): Die Quellachse war blind: Der AX-Extractor fasstaria-label,altundvaluezuAttributeundaria-labelledbywie<label>zuRelatedElementzusammen, und das Differential meldete alle diese Namen alsunknown. Der Extractor liest jetzt zusaetzlich die gewinnende Quelle ausname.sources(erste mit Wert; Kennung ausattribute/nativeSource), liefert sie ueberextract_ax_tree_with_name_sourcesneben dem Baum, undCdpDocument::with_chrome_name_sourcesreicht sie annames_by_sourceundSemantics::name_sourceweiter. Das grobeNameSourceausa11y-perception, dastext_alternatives,accessible_name,svg_rulesundinstructionslesen, bleibt unveraendert (Test). Rollenvergleich: Chromeimagegegenaccnameimgzaehlt als gleich. Korpus neu gemessen (35 Seiten):unknown56 → 0 (aria-label50,alt3,label2,aria-labelledby1), Rollenabweichungen 215 → 25. Nur Messseite,accnameunda11y-perceptionunberuehrt. -
Referenzset bewertet: sieben Live-Seiten mit Baendern, 2026-09-27 (Plan 47, Schritt 2): Baender aus drei Quellen je Seite — aktueller Score, Pruefung im Browser (Skip-Link, Gliederung, Alternativtexte, Formularbeschriftungen, Umbruch bei 320 px) und die veroeffentlichte Barrierefreiheitserklaerung —, vom Nutzer bestaetigt: gov.uk 75–92, gov.ie 75–92, casoon.de 90–100, mit.edu 70–89, bundesregierung.de 35–60, dm.de 15–40, berlin.de 15–40, basf.com 0–25. berlin.de schwankte zunaechst (Desktop 71 oder 27): Die 71 stammte von einer Bot-/Last-Zwischenseite, die das Werkzeug als Startseite bewertete — behoben durch das Folgen eines Meta-Refresh auf dieselbe URL; die echte Seite liegt stabil bei 27.
-
1.1.1: Icon in einem benannten Link ist kein fehlender Alternativtext, 2026-09-27 (Plan 47): Beim Pruefen der Referenzseiten meldete auditmysite auf www.mit.edu 7 „Image is missing alternative text” (High, Level A) — alles Inline-
<svg>-Icons ohnerole="img"in Links mit eigenem Namen („MIT@twitter”, „open search”, das Logo in „Massachusetts Institute of Technology”). Der Link traegt die Alternative; das Icon ist dekorativ, 1.1.1 erfuellt. axe laesst diesen Fall aus (svg-img-altgilt nur fuerrole="img"). Wegen der neuen Level-A-Kappung haette schon ein solcher Fehlalarm die Seite auf 89 begrenzt.text_alternativesnimmt jetzt namenlose Grafiken ohneurl-Eigenschaft (also nicht<img>) in benannten Links/Buttons aus; ein<img>ohnealtbleibt ueberall ein Verstoss (F65). Bekannte Unschaerfe: ein explizitesrole="img"sieht im Baum gleich aus. mit.edu 68 → 78. -
aria-prohibited-attr nennt die Rolle statt des Tags, 2026-09-27: Fuer
div/spanohnerolemeldete die Regel „prohibited on role ‘span’“. Die Regel erfasst in diesem Fall nur diese beiden Tags, deren implizite Rolle nach HTML-AAMgenericist — die Meldung nennt jetztgeneric. Gefunden an den 30aria-label-Spans auf casoon.de/leistungskatalog/(Plan 52). Indocs/accname-differential.mdisttext_transformjetzt als erwartete Abweichung gefuehrt (Entscheidung 2026-09-27:accnamebleibt beim DOM-Text). -
Menue-Buttons nur noch mit der Menue-Journey, 2026-09-27 (Plan 53): Ein Button mit
aria-haspopup="menu"wurde zweimal geprueft — als Menue (MenuOpen) und zusaetzlich vom Accordion-Erkenner als Aufklapp-Element (AccordionToggle, also mit der Disclosure-Journey). Nach den ARIA Authoring Practices ist das das Muster „Menu Button”, nicht „Disclosure”; die Disclosure-Journey beurteilte ihn nach Erwartungen, die er nicht erfuellen muss. Der Accordion-Erkenner bietet jetzt keinen Ausloeser mehr an, den der Disclosure-/Menue-Erkenner schon angeboten hat; die Erkennung im Berichtstext bleibt. -
Refresh-Zwischenseiten (Bot-/Lastpruefung) wurden als Seite auditiert, 2026-09-27 (Plan 47): berlin.de liefert einem Teil der frischen Browser-Sitzungen statt der Startseite eine 453-Byte- Zwischenseite („Einen Augenblick bitte / Just a moment please”, Varnish,
<meta http-equiv= "refresh" content="2; url=/~~delay/">,<dialog open>), die nach 2 s auf die echte Seite zurueckfuehrt. HTTP 200,readyStatecomplete, keine Mutationen — fuernavigateund die Stabilitaetspruefung fertig. Der Desktop-Pass auditierte in 8 von 12 Laeufen diese Zwischenseite (5 AX-Knoten, Barrierefreiheit 71 statt 27, Befunde fehlender main/banner-Landmark, bypass,dialog-name,2.2.1/meta-refresh); der Mobile-Pass sah danach die echte Seite.navigatefolgt jetzt einem Meta-Refresh mit hoechstens 5 s Verzoegerung (max. 3 Spruenge), bis ein neues Dokument interaktiv ist. Endet die Kette wieder auf der angefragten URL, war es eine Zwischenseite; fuehrt sie woanders hin (echte zeitgesteuerte Weiterleitung), wird die angefragte Seite neu geladen und ihr Refresh nach dem Laden mitwindow.stop()abgebrochen — sie wird so auditiert wie ausgeliefert, samt2.2.1/meta-refresh, statt wie bisher je nach Verzoegerung mitten in der Analyse wegzunavigieren. Nachgemessen: berlin.de 12 Laeufe nach dem Fix, Desktop/Mobile stets 27/27 (Zwischenseite in 7 davon aufgeloest); bundesregierung.de 4 Laeufe unveraendert 41/41; lokale Seite mit1; url=b.htmlbleibt auf a.html mit Meta-Refresh-Befund. Unit-Tests fuer das Parsen der Verzoegerung und den URL-Vergleich. -
accname-Differential: Korpuslauf und
text_transform-Klasse, 2026-09-27 (Plan 52):accname-diffwar auf eine URL beschraenkt; ein Korpus liess sich nur von Hand zusammenzaehlen. Das Kommando nimmt jetzt mehrere URLs und--url-fileund schreibt ab zwei Seiten einAccnameCorpus: Summen je Form und Namensquelle, Kreuztabelle beider Achsen, wiederkehrende Muster mit Vorkommen und Seitenzahl, die Einzelergebnisse und nicht geladene Seiten. Eine Seite, die nicht laedt, bricht den Lauf nicht mehr ab.Neue Form
text_transform: Namen, die sich nur in der Gross-/Kleinschreibung unterscheiden, werden mit dem berechneten Stil des Elements und seiner Nachfahren abgeglichen (DOM.resolveNodegetComputedStyle, nur fuer diese Kandidaten). Hat eines davontext-transformungleichnone, wird der Fall getrennt gezaehlt — im ersten Befund war das die haeufigste Ursache und haette jeden Korpus dominiert. Messung auf der Seite dieses Werkzeugs,accnamebleibt unberuehrt. Ohne Stil (compare) bleibt esmismatch. Unit-Tests fuer Klassifikation, Aggregat und CLI-Parsing.
-
Stabilitaetspruefung meldete immer „Budget ausgeschoepft”: jeder Audit galt als partial, 2026-09-27:
wait_for_page_stabilitywertet ein Promise aus, das zu einem Objekt{ status, waited_ms, mutation_count }aufloest — gebaut ohnereturn_by_value. CDP gab deshalb nur eine Objekt-Referenz zurueck,valueblieb leer, und jeder Aufruf fiel auf die Vorgabewerte:budget_exhausted, volles Budget, 0 Mutationen, gleich was die Seite tat. Gewartet wurde richtig; falsch war nur, was darueber berichtet wurde. Folgen:audit_qualitystand praktisch immer aufpartialmitpage_stability_budget_exhausted:2(so bei jedem casoon.de-Lauf), und die Journeys konnten nicht sehen, wie lange ein Klick tatsaechlich abgewartet wurde. Gefunden beim Umsetzen von Plan 53 (d). Nachgemessen: casoon.de und bundesregierung.decomplete; Wartezeiten im Trace jetzt echt (z. B. „stable after 201 ms, 0 mutations”). -
Disclosure-Befunde mit Unsicherheits-Hinweis statt laengerer Wartezeit, 2026-09-27 (Plan 53): Die Wartezeit nach einem Klick endet nach 200 ms ohne DOM-Aenderung; eine spaetere Reaktion wird nur erfasst, wenn zufaellig eine andere Aenderung das Fenster verlaengert — gemessen die Ursache der rund 8 % Lauf-zu-Lauf-Schwankung. Laenger zu warten kostete +57 % Disclosure-Zeit fuer ein geaendertes Urteil in rund 66. Stattdessen traegt der Trace jetzt je Klick Status, Wartezeit und Mutationen, und ein Befund aus einem Klick, dessen Warten vor dem Budget endete, bekommt
uncertainty: {kind: "late_reaction_possible", waited_ms}. Nach dem #406-Muster: kanonischeskind+ Rohwert,finding_uncertainty_textals einzige Textquelle, im PDF lokalisiert an den Befund gehaengt; unsichere und sichere Befunde werden dort nicht zusammengefasst. -
Natives
<details>: ein Journey-Lauf je Ausloeser, keine Fehlalarme mehr, 2026-09-27 (Plan 53): Ein neuer Chrome-Fixture-Test (natives_details_erreicht_die_disclosure_journey) fand drei Luecken. Erstens botpatterns::accordionjeden<summary>und jeden ARIA-Button mitaria-expandedausserhalb von Navigation/Banner zusaetzlich alsAccordionTogglean — der dieselbedisclosure_journeystartet wie der schon vorhandeneDisclosureToggle-Kandidat. Jeder solche Ausloeser wurde zweimal durchgeklickt, seine Befunde doppelt gemeldet. Das Akkordeon ueberspringt jetzt Ausloeser, die bereits einenDisclosureToggle-Kandidaten haben; die Zahl der Disclosure-Journeys je Seite sinkt entsprechend. Zweitens traegt<summary>in<details name>die RolleDisclosureTriangleGrouped: das Akkordeon meldete dafuer auf jedem exklusiven Akkordeonaccordion-trigger-not-button(Medium, 4.1.2), und die Journey bestimmte keinen gesteuerten Bereich. Drittens bekam ein offenes natives<summary>accordion-no-controls(Low) — ein natives Element braucht keinaria-controls; die Pruefung gilt jetzt nur fuer die Rollebutton. Die Laufzeitschwankung der Disclosure-Urteile ist an Fixtures vermessen und in Plan 53 dokumentiert (Ursache: das Beruhigungsfenster endet 200 ms nach dem Klick, spaetere Reaktionen werden nur zufaellig erfasst); das Zeitverhalten bleibt unveraendert, weil die Abhilfe auf acht Live-Seiten +57 % Journey-Zeit kostet. -
Geteilte Regeln bekommen eigene Taxonomie-Eintraege, 2026-09-27 (Plan 56): Auf
landmarks_and_listsstand ein Befund „Missing semantic structure” mit drei unverwandten Fundstellen — fehlende Navigations-Landmark, leere Liste, Listeneintrag ausserhalb einer Liste. Ursache:wcag_group_keygruppiert nach der Regelkennung nur, wennLEGACY_WCAG_MAPsie kennt, sonst nach dem WCAG-Kriterium. Keine der 1.3.1-Kennungen ausa11y-rules(vier Listen-, drei Tabellen-, drei Ueberschriftenregeln) stand dort, alle fielen ina11y.structure.missing. Ebenso liefdocument/lang-invalidunter „Missing language declaration”, undkeyboard/positive-tabindexteilte sicha11y.focus_order.weakmit der 2.4.3-Gruppe, womit zwei Befunde dieselbe Kennung tragen konnten. Jetzt hat jede dieser Kennungen einen eigenen Eintrag (Titel deutsch und englisch, dasselbe Kriterium, Score-Bereich);lists/item-outside-listundzoom/viewport-scale-limitednutzen die Eintraege der abgeloesten eigenen Regeln (a11y.list_structure.missing,a11y.viewport_zoom.restricted). Ein Test haelt fest, dass keine geteilte Kennung mehr im 1.3.1-Sammelbucket landet.Der Radio-Button ausserhalb einer Gruppe trug die axe-Kennung
definition-list: Die eigene Regelinfo_relationshipsstempelte alle drei Teilpruefungen (Tabelle, Liste, Radio-Gruppe) mit der Kennung ihres Laufs. Die Radio-Pruefung meldet jetzt alsradio-group(a11y.radio_group.missing); die Korpus-Erwartung ist nachgezogen.Der Accessibility-Score bleibt gleich:
AccessibilityScorergruppiert nachViolation.rule, also dem Kriterium, nicht nach der Taxonomie. Der Abzug der neuen Eintraege ist der des Sammeleintrags, unter dem die Kennung vorher lief; nur die beiden wiederverwendeten Eintraege bringen ihren frueheren eigenen Abzug mit. Bewegen kann sich die Bereichsaufschluesselung (accessibility_score_breakdown), um ein bis zwei Punkte — Ueberschriftenbefunde zaehlen jetzt unter „Heading structure” statt „Semantics”. axe-Vergleich: Spearman −0,536 unveraendert, keine Seite bewegt. -
Eindeutige Selektoren in allen eigenen Seitenregeln, 2026-09-27 (Plan 57): Etliche JS-Regeln bauten ihren Selektor als
tagbzw.tag#id(teils mit Klasse). Fundstellen werden nach (Regel, Selektor) zusammengelegt — also fielen alle id-losen Elemente desselben Tags zu einer Fundstelle („a”, „button”, „div”) zusammen: zu niedrige Zaehlung, nicht auffindbar. Betroffen waren 2.4.11/2.4.12 Focus Not Obscured (auch die Fokus-Walk-Pruefung), 1.3.2 Meaningful Sequence, 3.3.8 Accessible Authentication (Feld, Formular, CAPTCHA-Widget), 2.2.2 Pause/Stop/Hide, 2.5.3 Label in Name, 1.4.11 Non-text Contrast (CSS), 1.4.13 Content on Hover, 2.1.1 Click-Handler, 1.4.1 Use of Color und 3.1.2 Language of Parts (einstufigesnth-of-type). Alle nutzen jetzt den vorhandenen__amsCssSelector(js_helpers::CSS_SELECTOR_JS); auch 2.5.5/2.5.8 geben ihre eigene Kopie (targetSelector) dafuer auf. Elemente mit id behalten die Formtag#id, die Korpus-Erwartungen bleiben gueltig. Die Kind-Bezeichner in der Meaningful-Sequence-Meldung bleiben kurz — sie sind Text, keine Fundstelle. Korpus, Score-Baender und Integrationstests gruen; axe-Vergleich unveraendert (Spearman −0,536), keine Seite bewegt. -
2.5.8 Target Size: Abstands- und Inline-Ausnahme, 2026-09-26 (Plan 47, axe-Vergleich):
target_size_minimummass nur das Rechteck eines Ziels. WCAG 2.5.8 nimmt aber zwei Faelle aus: ein zu kleines Ziel, um dessen Mitte ein 24-px-Kreis kein anderes Ziel (und keinen Kreis eines anderen zu kleinen Ziels) schneidet, und ein Link im Fliesstext. Beides fehlte — obwohl der Doc-Kommentar der Regel die Inline-Ausnahme nannte. Jeder einzeln stehende flache Button und jeder Link in einem Satz galt als Verstoss; so auch „Send Message” und der Skip-Link aufperfect.html. Jetzt umgesetzt; 2.5.5 (AAA) bekommt nur die Inline-Ausnahme, weil es keine Abstands-Ausnahme kennt. Der Inline-Baustein ist gemeinsam (INLINE_TARGET_JS).Die Korpus-Fixture
keyboard_and_targetserwartete einen einzelnen 16-px-Button als Verstoss — die Grundwahrheit bildete den Fehler ab. Sie traegt jetzt zwei benachbarte 16-px-Buttons (Verstoss), einen einzelnen mit Abstand und einen Link im Fliesstext (beide bestanden, der Link auch fuer 2.5.5). axe-Vergleich: Spearman −0,492 → −0,536.Zwei Nachbesserungen, gefunden am Abgleich mit axe auf
perfect.html: Die Inline-Pruefung zaehlt nur den Inline-Inhalt der eigenen Zeile ohne andere Ziele — mit dem ganzen Blocktext galt ein Skip-Link direkt unter<body>als „im Fliesstext”. Und der Kreis darf das Rechteck jedes anderen Ziels nicht schneiden, auch wenn das selbst zu klein ist. Dazu eindeutige Selektoren (tag#idodernth-of-type-Pfad): mit dem blossen Tag-Namen fielen alle Links einer Seite zu einer Fundstelle „a” zusammen.perfect.htmlmeldet jetzt dieselben vier dicht stehenden Links wie axe (Skip-Link, Home, About Us, Contact) und laesst den frei stehenden Button aus. -
Geteilte Regeln melden im JSON Englisch: a11y-Familie auf 0.11.0, 2026-09-26: Beim Pruefen der axe-Abweichung von
landmarks_and_listsfielen deutsche Saetze im JSON auf („Der Listeneintrag steht außerhalb einer Liste.“).run_shared_rulesuebernimmt die Meldung ausa11y-rulesunveraendert, und bis 0.10 formulierte das Crate deutsch — betroffen waren alle 16 eingebundenen geteilten Regeln, ein Verstoss gegen die Regel „JSON ist kanonisch Englisch” (#406).a11y-rules0.11.0 formuliert Befundtexte und Hinweise englisch und stand bereits auf crates.io;a11y-rules,a11y-report,a11y-domundaccnamesind von 0.10.0 auf 0.11.0 gehoben, ohne API-Anpassung. Das deutsche PDF zeigt die Fundstellen-Meldungen damit englisch — wie die der eigenen Regeln schon immer; Titel und Erklaerungen bleiben lokalisiert. Die zu grobe Einordnung der geteilten Listen-Regeln in die Taxonomie steht als Plan 56. -
Player-Bedienelemente von
<video controls>sind kein Autoreninhalt, 2026-09-26 (Plan 47, axe-Vergleich): Die groesste Abweichung im neuen axe-Vergleich warmedia_and_motion: auditmysite 49 (Critical), axe nichts. Der Critical kam aus Chromes eigener Player-Oberflaeche: Mitcontrolslegt der Browser Wiedergabe-, Stumm- und Vollbild-Button und eine Zeitleiste alssliderohnevaluenowaus seinem Shadow DOM in den Accessibility-Tree. Die ARIA-Regeln meldeten die Zeitleiste als „Required ARIA attribute missing” (Critical) und „Slider is missing accessible value” (High) — auf jeder Seite mit einem Video, gekappt auf 49, und von keinem Autor behebbar. Behoben ina11y-perception0.2.1:AXTree::iter()laesst alles unterhalb vonVideo/Audioaus, fuer alle WCAG-Regeln.media_and_motion49 → 85, die drei eingebauten Maengel bleiben gefunden. Die Fixture stand in der Critical-Klasse der Score-Baender — nur wegen des Fehlalarms; sie ist dort entfernt. axe-Basislinie neu: Spearman −0,462 → −0,492, einzige bewegte Seite ist diese. -
Live-Referenzset und axe-Vergleich fuer die Score-Kalibrierung, 2026-09-26 (Plan 47, Schritte 2 und 3): Referenzset:
tests/fixtures/reference_sites.jsonfuehrt acht oeffentliche Seiten; jede bekommt ein Band aus einer manuellen Pruefung (nicht aus dem Werkzeug), mit Datum.tests/reference_sites_test.rs(Chrome + Netz) meldet jede Seite ausserhalb ihres Bands und laeuft als neuer Release-Schritt 0 vor dem Tag (CLAUDE.md). Seiten ohne Pruefung werden uebersprungen.axe-Vergleich:
scripts/axe-divergence.pylaesst auditmysite und axe-core (WCAG-2.2-A/AA-Tags) ueber alle 45 Fixture-Seiten laufen und haelt je Seite Score und axe-Verstoesse nach Schwere fest. Kennzahl ist die Rangkorrelation (Spearman) zwischen Score und gewichteter axe-Last; die Basislinie liegt intests/fixtures/axe_divergence_baseline.json: −0,46 — gleiche Richtung, maessig stark. Ein spaeterer Lauf nennt die Veraenderung der Korrelation und jede Seite, deren Score sich um ≥ 5 bewegt, obwohl axe dasselbe sieht. Uebereinstimmung ist nicht das Ziel; ein Scoring-Umbau, der die Korrelation deutlich verschlechtert, soll auffallen. -
Score-Baender und eine neue Kappung: Level-A-Verstoss ist nicht SEHR GUT, 2026-09-26 (Plan 47, Schritt 1b): Zum ersten Mal steht neben dem Scorer eine Erwartung, die nicht aus ihm selbst kommt. Entschieden in Zertifikatsbegriffen: eine praktisch saubere Seite ≥ 95; genau ein High-Verstoss auf Level A, sonst (fast) sauber, 75–89; mindestens ein Critical ≤ 49.
tests/score_calibration_test.rsprueft das an 21 Fixtures gegen echtes Chrome, auf Standardniveau AA wie ein Nutzer-Audit.Das mittlere Band scheiterte am bisherigen Scorer: Ein fehlender Alt-Text auf sonst sauberer Seite ergab 92, also SEHR GUT — fuer eine Seite, die Level A nicht erfuellt und damit nicht konform ist. Neue Kappung neben den bestehenden (Critical → 49, ≥ 5 Critical/High → 92): ein High- oder Critical-Verstoss gegen ein Level-A-Kriterium begrenzt auf 89. Dieselbe Kappung wirkt in der JSON-Aufschluesselung nach Bereichen, damit die nicht wieder vom Score abweicht (Plan 37). Betroffen: jede Seite mit einem Level-A-High-Befund und sonst hohem Score — sie faellt von SEHR GUT auf GUT.
test_score_with_errorspinnte genau das alte Verhalten (95, Note A) und ist angepasst; der Invarianten-Generator mischt jetzt Level A und AA, damit auch die 92er-Kappung geprueft wird. -
Invarianten fuer den Accessibility-Score, 2026-09-26 (Plan 47, Schritt 1a): Der Score hatte keinerlei Pruefung, die nicht von seinen eigenen Konstanten abhing. Fuenf Eigenschaften, die keine Abstimmung brechen darf, laufen jetzt als Tests ueber je 2000 deterministisch erzeugte Befundmengen (katalogisierte und unbekannte Regeln gemischt): Score in 0–100; ein weiterer Befund hebt ihn nie; eine hoehere Severity hebt ihn nie; die Kappungen (Critical → ≤ 49, ≥ 5 Critical/High → ≤ 92) gelten fuer jede Menge; nur die leere Menge ergibt 100. Alle halten. Einzige Beobachtung: Die Strafen werden in
HashMap-Reihenfolge summiert, dieselbe Menge kann in der sechsten Nachkommastelle abweichen — irrelevant fuer den ganzzahlig angezeigten Score, die Tests tolerieren 1e-3. Was eine Seite erreichen soll, sagen die Invarianten nicht; dafuer folgen die Score-Baender. -
Zwei Detection-Fehler, gefunden beim Kalibrieren der Scores, 2026-09-26 (Plan 47): Bevor Score-Baender festgelegt werden, lief das Tool ueber alle 43 Fixtures. Zwei Befunde waren falsch.
1.3.5 Identify Input Purpose las die falsche Quelle. Die Regel pruefte die AX-Eigenschaft
autocomplete— das istaria-autocomplete(list/inline/both); das HTML-Attributautocompletelegt Chrome im Baum gar nicht ab. Folge: jedes korrekt ausgezeichnete Personendaten-Feld galt als „lacks autocomplete attribute” (Medium), sichtbar schon anperfect.htmlmitautocomplete="name"/"email"; eine Combobox mitaria-autocomplete="list"haette „Invalid autocomplete value” bekommen. Die Regel laeuft jetzt als DOM-Seitenregel (check_input_purpose_with_page, wie 1.3.6 und 3.3.7) und liest das Attribut; die Beschriftung naehert den Accessible Name an (aria-label → aria-labelledby → label → title → placeholder). Die Einordnung ist eine reine Funktion mit Unit-Tests.2.4.7 Focus Visible meldete eine Seite ohne fokussierbare Elemente als Verstoss (High). 2.4.7 gilt fuer „any keyboard operable user interface” — gibt es keine, ist das Kriterium nicht anwendbar; axe-core meldet dort nichts. #568 hatte den Befund bewusst wieder erreichbar gemacht, der Korpus erwartete ihn. Er bleibt, aber als Review-Hinweis (Low) ohne Wirkung auf den Score;
no_focus_targetserwartet jetztneeds_review. Betroffen waren 21 von 43 Fixtures.Detection-Korpus gegen echtes Chrome gruen.
perfect.html: 97 → 99 (verbleibend 2.5.8, ein 21 px hoher Button — nach WCAG 2.2 zutreffend). -
AAA-Liste im PDF ehrlich gezaehlt: 11 statt 17, 2026-09-26: Dieselbe Ueberzeichnung wie bei der A/AA-Quote (30/55): Das PDF fuehrte 17 AAA-Kriterien als „automatisch geprueft”, aber sechs davon koennen keinen Verstoss melden — 1.2.8, 2.2.3, 2.2.4, 2.2.5 und 3.1.3 liefern nur einen
untested-Eintrag, 2.4.12 nur einen Review-Hinweis. Sie stehen jetzt inHINT_ONLY_CRITERIA; die Liste zeigt 41 Kriterien (30 A/AA + 11 AAA). Anders als die A/AA-Faelle kommen sie nicht auf die Liste der manuell zu pruefenden Kriterien, weil die wie die Quote auf A/AA bezogen ist. Der Guard-Test unterscheidet das jetzt. Geprueft ueber--debug-typ(DE/EN). -
Journey-Reste aus Plan 53: Skip-Link-Ziel, eingegrenzter Inhalt, Lesefehler, 2026-09-26: Drei Maengel aus der Offen-Liste von Plan 53.
Skip-Link. Die Journey pruefte nur, ob der Fokus
bodyverlassen hat. Das meldete ein nicht fokussierbares<main>als High-Befund, obwohl der Browser den Startpunkt der Tab-Navigation versetzt und der naechste Tab im Inhalt landet — und liess einen Fokus durchgehen, der irgendwohin sprang, nur nicht zum Ziel. Jetzt wird der Link zuerst fokussiert (wie bei Tastaturbedienung), das Ziel aus demhref-Fragment aufgeloest und geprueft, ob der Fokus darin oder dahinter steht; bleibt er auf Link oderbody, entscheidet ein Tab. Ein Fragment ohne Element ist ein Befund. Fuenf Fixtures, gegen echtes Chrome getestet.Disclosure. „Inhalt erschienen” war seitenweit — ein nachgeladenes Bild am Seitenende machte aus „ausgeklappt gehoert, nichts zu lesen” ein Bestehen. Ist der gesteuerte Bereich bekannt (
aria-controls, oder das<details>um ein<summary>), zaehlt nur noch, was darin erscheint oder verschwindet. Im Feld reicht das weniger weit als erhofft: auf 12 Seiten mit 138 Disclosure-Journeys liess sich der Bereich nur 8-mal bestimmen. Der Trace vermerkt je Journeyscopedoderpage_wide.quantity_stepper. Scheiterte ein CDP-Aufruf, wurde aus
focus_and_confirm/is_native_inputfalse— und daraus ein High-Befund „nicht per Tastatur bedienbar”. Nicht lesbare Werte stehen jetzt alsnot_observableim Trace, ohne Befund. -
Journey-Budget 15 s, harte Grenze je Journey, 2026-09-25 (Plan 53): Die Hoehe des Budgets war offen. Gemessen an 48 oeffentlichen Startseiten ohne Grenze und dann je Budgethoehe nachgerechnet (Deadline vor jedem Start geprueft, wie im Code): mit 5 s liefen 22 % der Seiten ins Limit und nur 55 % der Befunde wurden erfasst — allein der Tab-Walk braucht konstant rund 2 s. 15 s erfassen 92 % (10 s: 83 %, 20 s: 98 %) und kosten im Mittel +1 s je Seite, weil das Budget nur auf schweren Seiten greift; der Median der interaktiven Phase liegt bei 2,8 s.
Derselbe Lauf zeigte, dass eine einzelne Journey unbegrenzt laufen kann: Die Deadline wird nur vor dem Start geprueft, und haengt die Seite, wartet jeder CDP-Aufruf seine 30 s ab — auf www.dm.de brauchte eine Accordion-Journey 180 s. Jede Journey (Tab-Walk, Muster-Journeys, SPA-Navigation) laeuft jetzt unter einer Grenze von 10 s und wird sonst mit
journey_timeoutals fehlgeschlagen gefuehrt; die langsamste Journey ohne Haenger lag bei 5,6 s (Modal).Nebenbei behoben: Lief das Budget ab, wurden die uebrigen Kandidaten aus der unsortierten Liste als
budget_exhaustedeingetragen, obwohl seit der Sortierung nach Konfidenz die sortierte abgearbeitet wird — der Report nannte die falschen Journeys als uebersprungen.Mit dem hoeheren Budget kamen auf www.dm.de erstmals die Modal-Journeys an die Reihe — und der Audit dauerte danach 243 s statt 73 s, alle Throttling-Profile der Performance-Messung liefen in Timeouts. Ursache (Plan 55): Escape schliesst keinen der drei Info-Dialoge, die Journeys lassen sie offen, und die Folgephasen auf demselben Tab konfigurieren Drosselung und Cache vor ihrem eigenen Neuladen — unter CPU-Drosselung antwortete der Renderer mit drei offenen Dialogen nicht mehr. Ein JavaScript-Dialog war es nicht (geprueft). Nach der interaktiven Phase wird der Tab jetzt auf
about:blankgesetzt; alle spaeteren Phasen navigieren ohnehin selbst zur URL. dm.de: 87 s, Performancecompleted, alle drei Profile gemessen. -
Ansage-Struktur nach a11y-perception, Woerter bleiben hier, 2026-09-25:
announcer.rshat zwei Dinge in einem Zug getan: entschieden, woraus eine Ansage besteht, und gleich gesagt, wie das auf Deutsch heisst. Die erste Haelfte ist keine Eigenschaft von auditmysite – dass die Ebene einer Ueberschrift in die Rolle gehoert, dassexpanded=false“eingeklappt” zu sagen hat undrequired=falsenichts, dass “fokussierbar” nur dort etwas hinzufuegt, wo die Rolle es nicht schon verraet: das gilt in jeder Sprache und fuer jede Oberflaeche. Sie liegt jetzt ina11y-perception 0.2.0alsannounce() -> Announcement(Name, Rolle, Zustaende als benannte Teile,AnnouncedRole/AnnouncedState). Hier bleibt die Zuordnung auf die Locale-Schluessel und das Trennzeichen – 16 Rollen, 13 Zustaende, dieselben Schluessel wie vorher, keine Aenderung anlocales/. Der Renderer ist von 118 auf 89 Zeilen Code geschrumpft; der Gewinn ist weniger die Zahl als das, was hier nicht mehr entschieden wird.Ein Verhaltensunterschied, klein und bewusst:
levelwird jetzt alsu8gefuehrt statt als roher Zeichenkettenwert. Eine Ueberschrift mit einer nicht-numerischen Ebene wird damit als “Ueberschrift” angesagt statt als “Ueberschrift Ebene”. Chrome liefert levelals Zahl, der Fall ist also theoretisch.Neuer Waechter
every_announced_role_and_state_has_a_german_word: Der Compiler erzwingt, dass jede Variante im Match steht, aber nicht, dass der i18n-Schluessel existiert –I18n::tgibt einen fehlenden Schluessel unveraendert zurueck, und “Suche, sr-role-textbox” waere sonst durchgegangen. Geprueft:cargo test --lib screen_reader(44 ok),cargo clippy --all-targets -D warnings,cargo fmt. Die vier bestehenden Snapshot-Tests sind unveraendert gruen – das ist der Beleg, dass die Ansagen Zeichen fuer Zeichen dieselben sind. -
Gepinnte Toolchain und ein schlankeres Crate-Paket, 2026-09-25 (Plan 28): Lokal, CI und Release bauten mit dem jeweils aktuellen
stable. Jetzt pinntrust-toolchain.tomlRust 1.98.1 (die aktuelle Stable-Version, eine Punktversion ueber dem im Plan genannten 1.98.0) mitclippyundrustfmt. Ein MSRV-Versprechen gibt es bewusst nicht,Cargo.tomlbekommt keinrust-version. Das README-Badgerust-1.75+behauptete eine nie gepruefte Untergrenze und nennt jetzt die gepinnte Version.dtolnay/rust-toolchainliest die Datei nicht. Es installiert, was im Ref oder im Pflicht-Inputtoolchainsteht, und setzt es perrustup default. Die Datei haette das zwar ueberstimmt, abercomponents/targetswaeren auf der falschen Toolchain gelandet, und die Version haette doppelt gepflegt werden muessen. Die Workflows rufen deshalbrustup toolchain installohne Argument auf; das installiert Version, Profil und Komponenten aus der Datei. Der Release-Build ergaenzt sein Matrix-Target perrustup target add.CARGO_INCREMENTAL=0, das die Action bisher setzte, steht jetzt imenvbeider Workflows.Cargo.tomlhat eineexclude-Liste:.claude/,.github/,.githooks/,docs/,scripts/,tests/, die beiden Zusatzdokumente undrust-toolchain.toml. Das Paket schrumpft von 623 auf 386 Dateien. Die Muster sind mit/verankert, denn die Liste aus dem Plan haette mit einem nacktentests/still auchsrc/output/tests/aus dem Paket entfernt. Jeder Pfad, deninclude_str!/include_bytes!referenziert, ist im Paket; die einzige Ausnahme ist eine Test-Fixture unter#[cfg(test)].cargo package --lockedlaeuft ohne--allow-dirtydurch, und das Binary aus dem entpackten Paket installiert sich und antwortet auf--help. Den ersten CI-Lauf mit der gepinnten Toolchain gab es noch nicht. -
Browser-Tests laufen im Release-Tor fuer den getaggten Commit, 2026-09-25 (Plan 30):
release.ymlruftci.ymlals wiederverwendbaren Workflow auf und baut erst, wenn dieser gruen ist. Der Kommentar inci.ymlbehauptete,browser-smokeundcoverageliefen dabei nicht mit, weil ihrif:nurpush/pull_requestzulaesst. Das stimmte nicht: In einem aufgerufenen Workflow gehoert dergithub-Kontext dem Aufrufer,event_nameist also daspushdes Tags, nieworkflow_call. Beide Jobs liefen im Release-Aufruf mit, nur aus Zufall, und eine aufworkflow_callgezielte Bedingung haette nichts unterschieden.Jetzt steht es ausdruecklich da:
browser-smokehat keine Job-Bedingung mehr und laeuft bei jedem Aufruf, also auch fuer den getaggten SHA.coverageist eine Messung und kein Tor und wird ueber!startsWith(github.ref, 'refs/tags/')fuer Tags uebersprungen. Die Kommentare inci.ymlundrelease.ymlbeschreiben das tatsaechliche Verhalten. Ein Contract-Test intests/release_contract_tests.rsprueft beides. Gegenprobe: Mit der alten Bedingung anbrowser-smokeoder ohne die Tag-Bedingung ancoverageschlaegt er fehl. Einen echten Tag-Lauf gab es dafuer noch nicht, er steht beim naechsten Release aus. -
Best-Effort-Aufraeumfehler nicht mehr unsichtbar, 2026-09-25 (Plan 31):
cargo judge errorsmeldete sechs Produktionsstellen in vier Dateien, die ein I/O-Ergebnis kommentarlos verwarfen. Keine davon darf den Audit abbrechen, aber zwei koennen unbemerkt Dateien liegen lassen. Das Loeschen des heruntergeladenen Browser-Archivs nach dem Entpacken und das Loeschen der.partial-Datei nach einem fehlgeschlagenen atomaren Schreiben protokollieren jetzt einwarn!mit Pfad; Installationsergebnis und urspruenglicher Schreibfehler bleiben unveraendert. Der Flush der Statuszeile „Checking for sitemap…” protokolliert auf Debug-Level, und nur wenn stdout ein Terminal ist — eine Pipe oder geschlossene Ausgabe ist eine gueltige Umgebung, kein Fehler. Das Aufraeumen von PDF-Screenshots und Evidence-Ausschnitten laeuft ueber einen Helper,remove_temp_file, derNotFoundals Erfolg wertet (die meisten Evidence-Plaetze werden nie geschrieben) und andere Fehler auf Debug-Level meldet, damit ein normaler Lauf still bleibt.Geprueft mit einem Unit-Test fuer den Helper (fehlende Datei bleibt still;
remove_fileauf ein Verzeichnis liefert einen Fehler, der nichtNotFoundist), dem bestehenden Atomic-Write-Test, clippy fuer beide Feature-Sets,cargo fmt --checkundcargo test --no-default-features.cargo judge errorssinkt von 17 auf 11swallowed-result-Funde; der Rest liegt in Tests und bleibt bewusst stehen, ebenso absichtlich verlustbehaftete Parse-Konvertierungen. -
Desktop- und Mobile-Score ueber dieselben Regeln, 2026-09-25 (Plan 46): Plan 46 vermutete, der zweite Viewport-Durchlauf bewege den Barrierefreiheits-Score nie. Gemessen an 35 oeffentlichen Startseiten stimmt das nicht: bei 24 von 35 weichen Desktop und Mobile ab, teils stark (berlin.de 74/26, kit.edu 46/26). Ein Wiederholungslauf ueber acht davon war auf sechs exakt gleich — das Signal ist ueberwiegend echt: Seiten liefern je Breakpoint anderes Markup, und die abweichenden Befunde kommen aus gewoehnlichen Strukturregeln (
interactive_name,alt_text, Landmarks, ARIA).Ein Teil des Abstands war aber ein Artefakt:
html_content_modelundreflowlaufen nur im Mobile-Durchlauf und flossen nur in den Mobile-Score. Beide urteilen ueber eine viewport-unabhaengige Eigenschaft der Seite (Roh-Markup; Umbruch bei 320 CSS-Pixeln). Der Desktop-Score wird jetzt ueber die Desktop-Befunde plus die Befunde dieser beiden Regeln berechnet; Ausfuehrungsvermerke und die Viewport-Kennzeichnung der Befunde bleiben unveraendert, denn gelaufen sind die Regeln weiterhin nur mobil. Nachgemessen: wohtml_content_modelder einzige Unterschied war, sind die Scores jetzt gleich (commerzbank.de 36/36, gov.ie 73/73, hamburg.de 20/20); berlin.de behaelt seinen echten Abstand. -
Automatisierungsquote ehrlich gezaehlt: 30/55 statt 39/55, 2026-09-25: Als automatisiert galt jedes Kriterium, fuer das der Regelkatalog einen Eintrag hat — auch wenn die Regel nur Pruefhinweise oder einen
untested-Eintrag erzeugen kann. Ein Kriterium, das das Werkzeug nicht als verletzt melden kann, prueft es nicht automatisch. Neun Kriterien betrifft das: 1.2.2, 1.3.2, 2.1.2, 2.2.2, 2.4.11, 2.5.1, 2.5.2, 2.5.4 und 3.3.7. Sie stehen inHINT_ONLY_CRITERIA(src/wcag/coverage.rs), fallen ausautomated_criteria()heraus und zaehlen jetzt als manuelle Pruefung; 1.3.2, 2.1.2, 2.4.11 und 3.3.7 sind dafuer neu in der Manuell-Liste. Die Regeln laufen unveraendert weiter, ihre Hinweise bleiben im Report. Neue Zahlen: 30 automatisiert, 21 manuell, 4 weder noch (von 55). Mitbewegt: die Prinzip-Abdeckung (informativ, kein Score-Einfluss) und der EN-301-549-Anhang, der diese Klauseln nun als „manuelle Pruefung” statt „automatisch ohne Befund” fuehrt. Ein Guard-Test stellt sicher, dass jedes Hint-only-Kriterium eine Katalogregel hat, nicht als automatisiert zaehlt und in der Manuell-Liste steht.docs/PARITY_CONTRACT.jsonc,docs/PARITY_CONTRACT.md(stand noch auf WCAG 2.1, 50/36/10) und README nachgezogen. Geprueft:--debug-typ-Lauf gegen eine lokale Fixture (Pruefumfang „30 von ca. 55”, Manuell-Wolke 21),cargo test --lib,parity_contract,release_contract_tests, Clippy mit allen Features. -
Kriterien-Befunde auf blosse Technik-Praesenz entfernt, 2026-09-25 (Plan 45): Vier Regeln meldeten ein WCAG-Kriterium, sobald eine Technik auf der Seite vorkam, die mit dem Kriterium zusammenhaengen koennte — ohne beobachteten Mangel. 2.2.3 No Timing schlug auf jedes
setTimeout/setIntervalin einem Inline-Skript an (Debounce, Karussell-Takt, Analytics), 2.2.4 Interruptions auf jedesrole="alertdialog", 2.5.1 Pointer Gestures auf Zeichenketten wieontouchstart(auch reine Feature-Erkennung) und 2.5.4 Motion Actuation aufdeviceorientation/devicemotion/shakeim Skripttext — letzteres sogar als Verstoss statt als Pruefhinweis. Alle vier melden jetzt nur noch denuntested-Eintrag, dessen Text bereits sagt, was manuell zu pruefen ist. Bei 2.5.2 Pointer Cancellation entfaellt der zweite Zweig (irgendeinaddEventListener('mousedown'|'touchstart')im Inline-Skript); der Hinweis aufonmousedown/ontouchstartdirekt an einem Bedienelement bleibt. Eine Stelle fuer kriterienlose Informationssignale gibt es nicht, die Erkennung ist daher ersatzlos gestrichen. Die Pruefhinweise flossen in diereview-Zaehlung und von dort in Screenreader- und Risiko-Aggregation. Geprueft:cargo test --lib, Clippy mit allen Features, Detection-Corpus mit echtem Chrome gruen. -
Pruefziel auf WCAG 2.2 AA umgestellt, 2026-09-23: Das Werkzeug pruefte gegen WCAG 2.1 AA und zaehlte die 2.2-Kriterien, die es laengst pruefte, bewusst aus der Quote heraus. Bezugsgroesse ist jetzt WCAG 2.2 AA: 55 A/AA-Kriterien (2.1s 50, minus das gestrichene 4.1.1, plus die sechs neuen), davon 38 automatisiert. Die WCAG-2.1-Sicht, auf die sich das BFSG ueber EN 301 549 V3.2.1 beruft, bleibt als Anhang aus demselben Lauf erhalten; Kriterien, die erst 2.2 gebracht hat, sind dort weiterhin als solche gekennzeichnet, weil die Norm sie nicht abdeckt.
4.1.1 Parsing ist in WCAG 2.2 gestrichen und wird nicht mehr als Kriterium gefuehrt. Die beiden Pruefungen, die daran hingen, melden jetzt 4.1.2 — dieselbe Zuordnung, die axe-core fuer
duplicate-id-ariafuehrt. Die generische Dopplung (ids/duplicate, iframe-Variante) meldet nur noch, was auch ein Verstoss ist: eine doppelt vergebene ID, auf die ein IDREF zeigt (for,headers,aria-labelledbyund die uebrigen ARIA-Verweise). Dann ist die Beziehung nicht mehr eindeutig aufloesbar. Eine Dopplung, auf die niemand zeigt, verletzt seit dem Wegfall von 4.1.1 kein Kriterium mehr und erzeugt keinen Befund. axe-core hat seine Entsprechungen (duplicate-id,duplicate-id-active) aus demselben Grund entfernt.Scores und Quoten verschieben sich dadurch und sind mit aelteren Reports nicht direkt vergleichbar — ein Bezugswechsel, keine Regression. Geprueft an www.casoon.de (Single, PDF ueber
--debug-typund JSON): Quote 38/55,wcag_coverage.leveljetzt „WCAG 2.2 AA”,report-lintohne Befund. Beim Gegenlesen fiel ein Zaehlfehler auf: Der Pruefumfang-Text zaehlte 2.4.12 (AAA und neu in 2.2) in die A/AA-Quote hinein; gezaehlt wird jetzt nur, was auch im Nenner steht. Allehelp_urls zeigen jetzt aufWCAG22/Understandingbzw.WCAG22/Techniques; 2.5.5 dabei auf den 2.2-Namentarget-size-enhanced.3.3.8 Accessible Authentication (Minimum) ist als eigene Regel dazugekommen, womit die Quote auf 39/55 steigt. Ein Verstoss ist nur, was gemessen ist: Auf jedes Passwort- und Einmalcode-Feld wird ein synthetisches
paste-Ereignis mit einem Probewert gefeuert; blockiert gilt das Feld nur, wenn das Ereignis abgebrochen wurde und der Wert nicht im Feld landet. Ein Handler, der das native Einfuegen abbricht und den Text selbst bereinigt einsetzt, bleibt damit unbeanstandet. Ein interaktives CAPTCHA in einem Formular mit Passwort- oder Code-Feld ist ein Pruefhinweis, kein Verstoss, weil Objekterkennung und Alternativen aus dem DOM nicht entscheidbar sind; ein CAPTCHA im Kontaktformular gehoert nicht zu 3.3.8.autocomplete="off"am Passwortfeld wird bewusst nicht gemeldet — Browser ignorieren es dort.Beim Lauf des Detection-Corpus fiel auf, dass die
duplicate_id-Fixture noch eine unreferenzierte Dublette als Verstoss erwartete. Sie prueft jetzt beide Seiten: referenziert (label for) ist ein Verstoss, unreferenziert keiner. Geprueft: Detection-Corpus mit echtem Chrome komplett gruen,cargo test --all-features, Clippy fuer beide Feature-Sets, www.casoon.de (Regel laeuft, kein Befund, Quote 39/55,report-lintohne Befund).3.2.6 Consistent Help wird im Batch-/Sitemap-Modus seitenuebergreifend geprueft. Pro Seite erfasst
patterns::help_mechanismsdie Hilfe-Mechanismen ausserhalb vonmainin Dokumentreihenfolge: Kontakt-/Hilfe-/FAQ-Links,mailto:,tel:und bekannte Chat-Widgets, jeweils mit ihrem Seitenbereich (Kopf, Navigation, Seitenleiste, Fuss, schwebend). Inhalte inmainund unsichtbare Elemente bleiben aussen vor, sonst wuerde jeder Kontakt-Link in einem Artikel als Inkonsistenz zaehlen.audit::batch_consistencyvergleicht alle Mechanismen, die auf mindestens zwei Seiten vorkommen, mit der Platzierung der meisten Seiten. Eine Abweichung ist entweder ein Mechanismus in einem voellig anderen Bereich (disjunkte Bereichsmengen, ein zusaetzliches Vorkommen zaehlt nicht) oder eine vertauschte Reihenfolge zweier Mechanismen im selben Bereich. Ohne wiederkehrenden Mechanismus bleibt das Kriterium „manuell pruefen”. Im Einzelseiten-Modus steht 3.2.6 jetzt in der Liste der manuell zu pruefenden Kriterien (11 statt 10), mit deutschem Titel aus einer neuen Tabelle fuer die 2.2-Kriterien, die EN 301 549 V3.2.1 nicht enthaelt.Dabei fiel auf, dass das Batch-JSON die seitenuebergreifenden WCAG-Bewertungen (3.2.3, 3.2.4, 3.2.6, 2.4.5) gar nicht ausgab, obwohl
OUTPUT_CONTRACT.mdsie zusagt — sie standen nur im PDF.site_analysis.consistencytraegt jetztwcag_cross_pageundhelp. Geprueft: neuer Chrome-Integrationstest des Detektors (tests/help_mechanisms_detection_test.rs), Unit-Tests fuer Bereichs- und Reihenfolge-Abweichung, PDF-Test fuer die lokalisierte Abweichungsliste, Batch-Lauf www.casoon.de (12 Seiten): 2 wiederkehrende Mechanismen, keine Abweichung, deutscher Text in der Typst-Quelle korrekt.2.5.7 Dragging Movements steht in der Liste der manuell zu pruefenden Kriterien (jetzt 12). Ob eine Ziehbewegung eine Alternative mit einfachem Zeigen hat, ist Verhalten und nicht am Markup ablesbar. Ein Hinweis-Detektor (sortierbare Listen,
draggable) wurde bewusst nicht gebaut: Jede Regel mit WCAG-Bezug zaehlt hier als automatisiert, 2.5.7 waere damit in die Quote gerutscht, ohne dass je ein Verstoss belegbar ist. Die Quote bleibt 39/55.summary.wcag_coverage.wcag_version("2.2") macht die Bezugsversion maschinenlesbar. Reports von vor der Umstellung haben das Feld nicht und waren auf WCAG 2.1 bezogen. Beide JSON-Schemas kennen das Feld,schema_versionbleibt 2.0 (additiv).Beim Referenzlauf auf www.inros-lackner.de fiel ein Zaehlfehler aus dem 4.1.1-Umbau auf:
rule_outcomesmeldete fuerids/duplicate10 Befunde,violationsenthielt keinen. Der Filter auf referenzierte IDs verwarf die Befunde, der Vermerk ausa11y-ruleswurde aber unveraendert weitergereicht. Er wird jetzt um die verworfenen Befunde gekuerzt. Geprueft: Unit-Test,cargo test --all-features, Clippy fuer beide Feature-Sets, Referenzlaeufe casoon.de (Single + Batch) und inros-lackner.de mitwcag_version„2.2” und ohnereport-lint-Befund. inros-lackner.de steht bei Barrierefreiheit 47 (am 2026-09-20: 20); die zehn unreferenzierten doppelten IDs zaehlen nicht mehr. Wie viel des Sprungs darauf und wie viel auf Aenderungen an der Seite entfaellt, wurde nicht isoliert gemessen. -
Den docs.rs-Build in der CI nachstellen, 2026-09-23 (#588): Der Melder von #588 verwies auf eine Action im Beta-Test, die den Doku-Build im Sandbox-Image von docs.rs ausfuehrt — mit deren Nightly, deren Ressourcengrenzen und auf dem, was
cargo packageveroeffentlichen wuerde. Das ist die Frage, die der bestehende Doku-Schritt nicht beantworten kann: Er baut auf dem Runner, mit Stable und ohne Speicherdeckel.Der Anlass steht im Build-Log von 1.5.1: 4,75 GB von 6,4 GB verfuegbarem RAM, 1m35s von 15 Minuten. Seit
all-features = truedokumentiert docs.rs auchai-transparency, was den Abhaengigkeitsgraphen von 454 auf 565 Crates hebt. Ein Build, der an dieser Decke stirbt, faellt erst nachcargo publishauf, und eine veroeffentlichte Version laesst sich nicht ersetzen.Der Job laeuft nur auf Tags: Die Frage lautet „schafft docs.rs diesen Build”, und sie ist einmal zu beantworten, kurz bevor die Version publiziert wird. Auf jedem Pull Request kostete sie sieben Runner-Minuten und einen Cache-Platz fuer einen Commit, den niemand veroeffentlicht.
CLAUDE.mdfuehrt den Job jetzt als Pflichtschritt vorcargo publish.Die Action ist auf einen Commit gepinnt statt auf einen Tag — sie startet fremdes Docker in der CI — und der Job laeuft mit
continue-on-error: true, solange sie Beta ist. Ein neuesdependabot.ymlhaelt den Pin (und die uebrigen Actions) im Blick. -
Die restlichen rustdoc-Warnungen abgeraeumt, 2026-09-23: Nach #588 blieben sieben Warnungen stehen, die das Tor damals bewusst durchliess: zwei Verweise auf Namen, die es nicht (mehr) gibt (
NodeIdohne Pfad,RuleOutcomestattOutcome), drei Verweise aus oeffentlicher Doku auf private Items (defect_key,is_layout_only_role,score_intent_fit) — die stehen jetzt als Code statt als Link, denn ein Link, dem der Leser nicht folgen kann, ist keiner — und zwei nackte URLs incli/args.rs. Die letzten beiden bleiben nackt: Diese Doc-Kommentare sind clap’s--help-Text, bevor sie rustdoc sind, und spitze Klammern oder Backticks stuenden dann vor jedem Nutzer im Terminal. Dort steht jetzt ein#[allow(rustdoc::bare_urls)]mit genau dieser Begruendung.Das CI-Tor zieht entsprechend nach: statt
-D rustdoc::invalid_html_tagsjetztRUSTDOCFLAGS: -D warnings. Verifiziert mitcargo doc --all-features --no-deps(keine Warnung), clippy,cargo fmt --check, 1546 Unit-Tests und einem Blick auf--help, wo die Beispiel-URLs unveraendert nackt stehen. -
docs.rs baut mit allen Features, 1.5.1, 2026-09-22: Ohne
[package.metadata.docs.rs]baut docs.rs nur mitdefault = ["pdf"]. Alles hinter einem optionalen Feature — etwac2pa— fehlte damit in der veroeffentlichten Doku.all-features = truebehebt das. Die Einstellung wirkt erst fuer Versionen, die danach publiziert werden, daher 1.5.1 statt eines Nachtrags zu 1.5.0. -
Live-Regionen ueber die Zeit beobachtet, Journeys von Messartefakten befreit, 2026-09-22: Die Formularfehler-Journey fragte, ob nach dem Absenden eine
[aria-live]-Region existiert, die vorher nicht existierte. Damit war sie genau fuer die empfohlene Umsetzung blind: Wer den leeren Live-Container von Anfang an im Markup hat, bekam einFormErrorInvalidWithoutLiveRegionmit Severity High. Die Add-to-Cart-Journey verglich zwei Stichproben und sah deshalb keine Meldung, die erscheint und nach zwei Sekunden wieder verschwindet — bei Warenkorb-Feedback der Normalfall.Neu ist
interaction::live_regions: einMutationObserverueber Live-Regionen, auch in spaeter erzeugten Shadow Roots, zeichnet auf, dass eine Region Inhalt bekam, mit Zeitpunkt und Dringlichkeit. Beide Journeys werten diese Ereignisse aus. Laesst sich der Beobachter nicht installieren, entsteht kein Befund, sondern einlive_observer_unavailableim Trace — ohne Messung ist „nichts angekuendigt” keine Aussage ueber die Seite. Beobachtet heisst nicht vorgelesen: Das Modul belegt nur, dass der Browser den Anlass dazu hatte.Beim Nachpruefen am Korpus fielen weitere Journeys auf, die Messartefakte meldeten:
- Tabs:
element.click()verschiebt den Fokus nicht, die Pfeiltaste traf also nie die Tab-Liste. 20 von 22 Tabs-Journeys meldetenTabsSelectionNotMoved(High). Bewertet wird jetzt nur, wenn der Fokus auf dem betaetigten Tab steht. - Menue: Escape wird nur bewertet, wenn der Fokus im Menue steht, sonst traf die Taste die Seite und nicht das Menue.
- Modal: Die Journey liest jetzt
modalam geoeffneten Dialog. Nicht-modale Dialoge wurden bisher pauschal als High-Befund gemeldet; jetzt gilt der Fokus-Einschluss nur fuer modale Dialoge, undFocusTrapBackgroundNotHiddennur fuer Dialoge, die den Fokus einschliessen, ohne sich als modal auszuweisen. Stand der Fokus vor dem Oeffnen aufbody, istbodynach dem Schliessen die richtige Wiederherstellung. Ausserdem erzeugte die Kandidatensuche nie einen Ausloeser, solange kein Dialog im Baum stand — ein geschlossenes<dialog>hat keine Rolle. Ueber 171 gelaufene Seiten lief deshalb keine einzige Modal-Journey. - SPA-Navigation: Titel, URL, Ueberschrift und Fokus kommen aus zwei
AXSnapshots statt ausdocument.querySelector('h1'); damit zaehlen auch Shadow Roots undrole="heading". Die Ausgangsaufnahme wird vor jedem Versuch neu genommen, damit ein Fehlversuch dem naechsten keine Fokusbewegung unterschiebt. - Disclosure: Die Groessenordnung der nativen
<details>-Luecke ist nachgemessen: 22 von 238 Domains, nicht „280 von 663 Seiten”.
Verifiziert mit
cargo test --all-features --lib(1546 bestanden), drei neuen Chrome-Integrationstests (transiente Fehlermeldung, korrekter modaler Dialog, korrektes Menue — jeweils ohne Fehlalarm) sowie clippy-D warningsfuer beide Feature-Sets. - Tabs:
-
Rohe HTML-Tags in Doc-Kommentaren, 2026-09-22 (#588): docs.rs meldete abgeschnittene Seiten, etwa
PageHealthAnalysis: Doc-Kommentare nannten Elemente wie<iframe>ohne Backticks, rustdoc gab sie als echtes HTML aus, und der Streaming-Rewriter von docs.rs brach daran ab. 16 Stellen in sechs Dateien stehen jetzt als Inline-Code. Verifiziert mitcargo doc --all-features --no-deps: keineinvalid_html_tags-Warnung mehr. Die CI baut die Doku jetzt mit-D rustdoc::invalid_html_tags, damit neue Faelle schon vor dem Release auffallen. -
a11y-core 0.10.0 und die Ueberschriftenregeln abgegeben, 2026-09-21: Das Projekt hing auf
a11y-core0.6.0, veroeffentlicht war 0.10.0. Der Sprung ueber vier Minor-Versionen brachte keinen einzigen API-Bruch —dom_document.rsundwcag/shared.rsuebersetzen unveraendert —, aber zwoelf zusaetzliche Kennungen: fuenflandmarks/*,keyboard/skip-link-missing,zoom/viewport-missing,headings/h1-multiple,links/generic-name,aria/required-attribute-missingund die beidencontrast/*. Damit stehen 42 geteilte Kennungen zur Verfuegung.Ein Test fiel dabei, und zwar richtig: Er verlangte, dass mit
Semanticskeine Regel mehr mangels Faehigkeit ausfaellt. Das galt bei 0.6.0, weil es dort kein Tier 3 gab. Seit 0.7.0 melden die Kontrastregeln ohne gerenderte StileCapabilityMissing— das Prinzip „nicht gelaufen ist nicht bestanden”, angewandt auf auditmysite selbst. Die Zusicherung nennt jetzt die beiden Kennungen, statt eine Null zu verlangen, die nichts mehr bedeutet.Renderingueber CDP zu bedienen bleibt ein eigener Schritt.Mit
headings/h1-multiplefiel die letzte Luecke, an derwcag::rules::headingsnur teilweise abloesbar war. Das Modul ist geloescht, alle vier Pruefungen kommen aus dem geteilten Bestand,SHARED_RULESfuehrt 16 statt 12 Kennungen. Beim Abraeumen kam eine Fehlmessung ans Licht: Die AX-Fassung sortierte Ueberschriften nachnode_idals Text, womit „h10” vor „h2” stand und Spruenge in laengeren Seiten falsch bewertet wurden. Die geteilte Fassung laeuft in Dokumentreihenfolge ueber den DOM.Zwei gewollte Unterschiede bleiben: Mehrere
h1sind geteiltREVIEWstatt Verstoss und melden einmal statt je ueberzaehliger Ueberschrift — in HTML sind mehrereh1zulaessig. Und „leer” heisst geteilt „kein Text im Teilbaum, keinaria-label“; eine Ueberschrift, die nur ueberaria-labelledbyoder einaltim Bild benannt ist, meldet die geteilte Fassung zu Unrecht. Das ist als Befund fuera11y-corevermerkt, nicht als Rueckausnahme hier.Verifiziert: 1.716 Tests gruen,
clippyundfmtsauber, die Korpus-Erwartungheading_orderaufheadings/skip-levelgehoben. Der Vollstaendigkeitstest des Korpus war erneut der Waechter, der eine stehengebliebene Kennung gefunden haette. -
Wartbarkeits-Hotspot 1, 2026-09-20 (Plan 27):
src/output/pdf/single_report.rswar mit 3.717 Zeilen und 21 Aenderungen in 60 Tagen die am haeufigsten angefasste Datei des Projekts und mischte Risiken/Staerken, Problem-Profil, Score-Treiber-Tabelle, Subkategorie-Breakdown, Anhang, Findings, Root-Cause-Analyse und Roadmap.Herausgeloest ist das Problem-Profil-Cluster nach
src/output/pdf/problem_profile.rs(527 Zeilen): neun Funktionen, zwei Konstanten und zwei Typen. Der Schnitt war risikoarm, weil nichts davon von ausserhalbsingle_report.rsreferenziert wurde — nur drei Funktionen mussten von privat aufpub(super).single_report.rsfaellt auf 3.219 Zeilen; der Diff dort enthaelt ausser drei Importzeilen und zwei Sichtbarkeits-Anhebungen nur Loeschungen.Output-neutral verifiziert: der
--debug-typ-Lauf von www.inros-lackner.de hat davor und danach dieselbe Zeilenzahl und einen identischen Problemprofil-Abschnitt.Der Test-Helfer
test_report_view_modelwurde nicht dupliziert, sondern ueberpub(in crate::output::pdf)sichtbar gemacht — eine zweite Kopie einer 70-Zeilen-Fixture waere genau die Art Duplikat, die dieser Plan abbaut.Neubewertung fuer Hotspot 2:
normalized.rsist mit 4.553 Zeilen und 20 Aenderungen/60 Tagen jetzt die groesste Datei und der naechste Kandidat. Details im Plan. -
Darstellungsartefakte im PDF, 2026-09-20 (Plan 36): Sechs kleine, unabhaengige Defekte aus einem Review der beiden
--debug-typ-Quellen, jeder wenige Zeilen, keiner eine Produktentscheidung.- Anhang behauptete 36 automatisch gepruefte Kriterien und listete 56. Die Wolke trug den
Titel der WCAG-2.1-A/AA-Quote, enthielt aber zusaetzlich AAA und die WCAG-2.2-Kriterien, ohne
dass das irgendwo stand. Jetzt traegt sie ihre echte Zahl plus eine Zeile, die sie aufschluesselt.
Beim Schreiben ging die Aufschluesselung erst nicht auf – 36 + 4 + 17 = 57 gegen 56 gelistete –,
weil ein AAA-Kriterium zugleich WCAG-2.2-only ist und in beiden Gruppen zaehlte. Die drei Gruppen
sind jetzt disjunkt, und
the_three_appendix_groups_partition_the_automated_criteriapinnt die Summe. ◐(U+25D0) wurde still verschluckt. Kein gebuendelter Font deckt das Zeichen; Typst meldete “Dropping U+25D0” und fuenf Zeilen mittlerer Konfidenz begannen mit einer Luecke, wo alle anderen ein Symbol hatten. Eine Legende gab es ohnehin nicht. Die Skala ist jetzt ein Wort ([gemessen]/[wahrscheinlich]/[schwaches Signal]) – verifiziert: keine Typst-Warnung mehr, und die fuenf Zeilen tragen jetzt ihr Label.- Modul-Takeaway stand zweimal. Der
section-header-split-Body wurde zwei Komponenten spaeter als eigenes Label wiederholt. Neuinterpretation_beyond_takeaway: das Label zeigt nur noch, was der Kopf nicht schon gesagt hat. - “Header 0/10” in der Erfolgsfarbe. Der Akzent war auf Teal festgenagelt; er kommt jetzt aus
dem Verhaeltnis, ueber dieselbe
score_color-Funktion wie ueberall sonst. inros-lackner 0/10 ist rot, casoon 8/10 gruen. - “Details im Methodik-Anhang” fuehrte ins Leere. Der Anhang wiederholte denselben Satz;
welche Messungen verworfen wurden, stand nirgends, obwohl
execution.navigation.stabilityes weiss. Der Hinweis nennt sie jetzt selbst (“Betroffen: Desktop-Ansicht nach 1500 ms, Mobile-Ansicht nach 1500 ms”).mutation_countwird nur genannt, wenn es tatsaechlich gezaehlt wurde – der Wert faellt sonst auf 0 zurueck, und “noch 0 DOM-Aenderungen” waere ein Default, gedruckt als Messung. - Widerspruechliche DOM-Einordnung. “(Performance 75 Pkt stabil)” beruhigte ueber genau das, was die Massnahmenliste zwei Bloecke weiter oben priorisiert (“DOM-Struktur verschlanken”). Jetzt “bei derzeit tragfaehiger Performance” – beide Fakten, keiner widerspricht dem anderen.
- Anhang behauptete 36 automatisch gepruefte Kriterien und listete 56. Die Wolke trug den
Titel der WCAG-2.1-A/AA-Quote, enthielt aber zusaetzlich AAA und die WCAG-2.2-Kriterien, ohne
dass das irgendwo stand. Jetzt traegt sie ihre echte Zahl plus eine Zeile, die sie aufschluesselt.
Beim Schreiben ging die Aufschluesselung erst nicht auf – 36 + 4 + 17 = 57 gegen 56 gelistete –,
weil ein AAA-Kriterium zugleich WCAG-2.2-only ist und in beiden Gruppen zaehlte. Die drei Gruppen
sind jetzt disjunkt, und
-
„Wenig Text oben” mass Knoten- statt Seitenposition, 2026-09-20 (Plan 51): Der Befund sagt dem Kunden „Wenig sichtbarer Text im oberen Seitenbereich — Nutzer erhalten keine sofortige Orientierung”. Das ist eine Aussage ueber die gerenderte Seite; gemessen wurden die ersten 50 Knoten des Accessibility-Baums in Dokumentreihenfolge.
Nach dem Rollen-Fix aus Plan 50 blieben 93 von 2731 gecachten Seiten geflaggt, und diese Reste waren keine kleinere Version desselben Problems, sondern das Versagen der Naeherung. Auf www.inros-lackner.de sind die ersten 50 Knoten ein Skip-Link und vierzig Wrapper; die
article-Elemente stehen als leere Huellen vor ihrem eigenen Text. Baumreihenfolge ist dort ueberhaupt keine vertikale Reihenfolge, also haette auch eine Wrapper-ueberspringende Variante (gemessen: 13 statt 93) nur eine unkalibrierte Konstante gegen eine andere getauscht.Jetzt misst
JourneyModule::collectin der Seite: einTreeWalkersummiert den Text, dessen Box in der ersten Viewport-Hoehe liegt. In Dokumentkoordinaten (rect.top + scrollY), weil ein anderes Modul vorher gescrollt haben kann, und ohne Screenreader-only-Text — derselbe__amsIsVisuallyHidden-Helfer, den die In-Page-WCAG-Checks nutzen. Dasselbe Muster wie der bereits vorhandene<main>-DOM-Check des Moduls. Die Baum-Naeherung bleibt als Fallback fuer Aufrufe ohne Seite (analyze_journey, Tests), damit ein fehlender Messwert nicht als „kein Text” gelesen wird.Verifiziert in beide Richtungen mit drei Fixtures: Text hinter einem 3000px-Hero wird geflaggt, eine Seite mit Ueberschrift und Absatz oben nicht, und 79 Zeichen reiner Screenreader-Text oben zaehlen nicht als sichtbarer Text. www.inros-lackner.de wird nicht mehr geflaggt, Journey dort 65 -> 68; www.casoon.de unveraendert 91. Zwei Fixtures und ein End-to-End-Test in
tests/integration_test.rshalten beide Richtungen fest — die alte Naeherung konnte sie nicht auseinanderhalten, weil die Ueberschrift im Baum so oder so vorne steht. -
Flaky Fixture-Server, 2026-09-20 (Plan 48):
security_detection_corpus_matches_real_analyze_security_runfiel sporadisch mit einer FALSE-NEGATIVE-Meldung um – der schlimmstmoeglichen Form eines Flakes, weil sie sich wie eine echte Regression in der Security-Erkennung liest. Der Plan vermutete einen Port-Race;bindauf Port 0 war aber schon da.Die tatsaechliche Ursache: der Listener wird non-blocking gesetzt, damit die Accept-Schleife ein Shutdown-Flag pollen kann – und unter macOS/BSD erbt der akzeptierte Socket dieses Flag.
readundwrite_allkonnten alsoWouldBlockliefern, bevor der Request ueberhaupt da war, und beide Ergebnisse wurden mitlet _ =verworfen. Die Antwort ging ungeschrieben raus, der Client sah nichts,analyze_securityfiel auf leere Header zurueck (was fuer eine Seite, die HEAD und GET verweigert, richtig ist) und der Corpus-Diff meldete einen fehlenden Header.Belegt statt vermutet: eine Probe zeigte
read -> Err(WouldBlock)in jedem Lauf, und unter 16-facher Parallellast fiel der alte Test in 29 von 48 Laeufen um, mit exakt der Meldung aus dem Plan (cors_wildcard_credentials). Nach dem Fix 48 von 48 gruen.Der Fix: akzeptierte Verbindung zurueck auf blocking, Request-Kopf bis zum Ende lesen, jede Verbindung in einem eigenen Thread (eine Accept-Schleife, die auf einen Client wartet, verhungert alle anderen), und Transportfehler werden mitgeschrieben statt verschluckt. Der Test prueft jetzt
fixture.served() > 0, bevor er irgendetwas als Erkennungsfehler wertet – verifiziert mit einem simulierten Transportfehler, der jetzt als solcher gemeldet wird statt als FALSE NEGATIVE.Derselbe Server steckte sechsmal kopiert in den Integrationstests, jede Kopie mit demselben Race. Er liegt jetzt einmal in
tests/common/fixture_server.rs(298 Zeilen weniger). Die vier Chrome-gegateten Suiten laufen damit gruen.Dabei fiel ein zweiter, bereits vorhandener Flake auf:
test_concurrent_wait_for_stable_stays_within_its_timeout_budgetlegte sein 4,65-s-Budget um die gesamte Task inklusivenew_page(), obwohl nurwait_for_stablegemeint ist – auf einer ausgelasteten Maschine fiel die volle--ignored-Runde deshalb auf dem unveraenderten Code 2 von 2 Mal um. Die aeussere Schranke ist jetzt eine reine Hang-Sicherung; die eigentliche Zusicherung misst weiterhin nurwait_for_stable. Danach 3 von 3 volle Runden gruen. -
StaticTextwar fuer jeden text-messenden Leser unsichtbar, 2026-09-20 (Plan 50):AXTree::iter()filtert browser-generierte Rollen heraus,StaticTextdarunter. Fuer WCAG-Regeln ist das richtig – ein Befund darf nicht gegen einen Knoten gemeldet werden, den niemand geschrieben hat. Fuer alles, was Text misst, ist es falsch: auf einer echten Seite stehen die sichtbaren Woerter genau dort. Gemessen an zwei gecachten Baeumen: 72-76 % aller Zeichen in einemnamesitzen auf browser-generierten Rollen, undparagraph-Knoten tragen ueberhaupt nie einen eigenenname.Damit las
matches!(role, "StaticText" | "paragraph")ueberiter()auf jeder realen Seite nichts. Zwei Stellen waren betroffen:- Seitentyp-Klassifikation (
page_intent.rs):text_lenwar strukturell 0, also konnteapprox_words > 500nie greifen. Dabei fiel eine zweite tote Konstante auf: der Marketing-Fallback verlangte zusaetzlichapprox_words < 300, was mit einer Summe von 0 immer zutraf. Mit echten Wortzahlen waeren 222 Seiten von „Marketing” auf „Nicht erkannt” gefallen – ein Landing-Page-Default, der gegen eine nie gepruefte 300er-Schranke verliert. Die Klausel ist entfernt, weil sie nie in Kraft war. - Journeys „fruehe Inhalte” (
analysis.rs): lasStaticText | headingund sah damit nur Ueberschriften.LittleEarlyText– 20 Punkte Abzug auf Entry Clarity – feuerte auf 1595 von 2731 eindeutigen gecachten Seiten. Mehr als die Haelfte des Webs angeblich oben ohne Text.
Eine Definition statt Rollenlisten pro Call-Site:
AXNode::is_text()undAXTree::text_nodes()/visible_text_len()zaehlen den Text genau einmal –StaticTextund nichts sonst, weil Chrome denselben Lauf auf denInlineTextBox-Kindern wiederholt und der Name einer Ueberschrift oder eines Links aus denselbenStaticText-Nachfahren berechnet wird (gemessen: 30 von 44 Ueberschriften-Namen und 71 von 87 Link-Namen sind wortgleich mit einemStaticText).Verifiziert ueber 2731 eindeutige gecachte Seiten mit dem neuen
tests/page_intent_corpus.rs(#[ignore]-gated, liest einen lokalen Artefakt-Cache):LittleEarlyTextfaellt von 1595 auf 93, und 68 Seiten wechseln den Seitentyp – alle Richtung Editorial, 48 davon auf einem tatsaechlichen Blog, der vorher als Corporate durchging.Nicht mitgefixt: das Zeitfenster selbst.
LittleEarlyTextverspricht „wenig Text im oberen Seitenbereich”, misst aber die ersten 50 Knoten in Baumreihenfolge. Auf www.inros-lackner.de stehen dort der Skip-Link und vierzig Wrapper. Eine Variante, die reine Wrapper ueberspringt, wurde gemessen (13 statt 93 geflaggt) und verworfen: diearticle-Knoten dieser Seite stehen als leere Huellen vor ihrem eigenen Text, Baumreihenfolge ist dort also ueberhaupt keine vertikale Reihenfolge. Das braucht Geometrie statt einer neuen Konstante und steht als Plan 51. - Seitentyp-Klassifikation (
-
AXTreeohne Dokumentreihenfolge, 2026-09-20 (Plan 49):AXTree.nodesist eineHashMap, unditer()/headings()gabenvalues()zurueck – Hash-Reihenfolge, die Rust pro Prozess zufaellig setzt. Detektoren, die den Baum als Dokument lesen, widersprachen sich damit selbst: zwei aufeinanderfolgende Audits von www.inros-lackner.de aus demselben Binary, eines meldete UX 78, das andere UX 81, ohne dass sich an der Seite etwas geaendert hatte. Betroffen waren die Heading-Skip-Erkennung (last_levelsetzt Dokumentreihenfolge voraus) und Journeys “fruehe Inhalte” (iter().take(50)ueber eineHashMapsind fuenfzig beliebige Knoten, nicht der Anfang der Seite).CDP liefert
getFullAXTreebereits in Dokumentreihenfolge –from_nodeshat sie nur in dieHashMapgeworfen. Neues privates Feldorder: Vec<String>haelt sie fest;iter()unditer_all()laufen darueber. Faellt die Reihenfolge weg (Snapshot aus dem Cache, der vor dem Feld geschrieben wurde, oder ein direkter Schreibzugriff auf das oeffentlichenodes), wird sie auschild_idsper Tiefensuche ab der Wurzel rekonstruiert, Unerreichbares nach Id sortiert angehaengt – degradiert, aber deterministisch.Verifiziert: drei aufeinanderfolgende Laeufe ueber www.inros-lackner.de liefern identische UX-Befunde und Modulwerte. Journey faellt dabei von 68 auf 65, weil
take(50)jetzt tatsaechlich den Seitenanfang liest. Der bei Plan 43 bewusst ausgelasseneHeadingSkips-Fall steht jetzt inevery_penalty_states_its_own_reason_at_the_first_occurrence– er war nicht zu behaupten, solange h1 -> h3 nur je nach Lauf ein Skip war.Nicht mitgefixt: die
StaticText-Blindheit deriter()-Leser, die im “Related”-Abschnitt von 49 als zwei Call-Sites stand. Der Einzeiler (iter()->iter_all()) wurde umgesetzt, gemessen und wieder verworfen: er aenderte auf keiner der beiden Testseiten die Klassifikation, und ein Guard-Test dafuer bestand auch mit dem Defekt, weil der Keyword-Scan darueberStaticTextebenfalls nicht sieht. Das ist das eigentliche Problem und steht jetzt als Plan 50. -
Score-Hierarchie, 2026-09-20 (Plan 29): Der Report trug rund 30 verschiedene 0-100-Werte, 14 davon mit Note A-F. Der Gegenstand des Tools – Barrierefreiheit – war einer davon, mit 40 % Gewicht in einem anderen. Vier Entscheidungen, vier Commits, danach gilt eine Drei-Ebenen-Regel: Barrierefreiheit als Ueberschrift, die fuenf uebrigen gewichteten Module als bewerteter Kontext, alles andere als Indikator ohne Note.
- Note nur fuer Module, die den Gesamtwert tragen (D2): Sieben der dreizehn Module haben
weight_pct: 0, speisen also nichts, trugen aber dieselbe A-F-Note wie Accessibility. So stand “Dark Mode: F” – eine Note fuer ein optionales Produktmerkmal, das der Code selbst alsmeasurement_type: "optional"fuehrt – neben einem F fuer den Ausschluss von Screenreader-Nutzern.ModuleScoreEntry::newleitet Gewicht, Note und Band aus einer Quelle ab:gradeistOptionund nur bei Gewicht > 0 gesetzt,bandtraegt immer das kanonisch englische FIVE_BAND-Label. Gleiches fuer diegrade-Felder vonAiVisibilityAnalysisundSourceQualityAnalysissowie fuerdark_mode. Das PDF war nicht betroffen – es zeigte ohnehin Bandwoerter, die Noten steckten nur im JSON. - Zaehler statt Quote, wo der Nenner beliebig ist (D4, schliesst Plan 43 §3): Content
Visibility war
(Signale - Probleme) / Signale, also “Anteil der Checks, die nicht gemeckert haben” – ein Check mehr, der meist besteht, hob den Wert jeder Seite. Der Score entfaellt ersatzlos; berichtet werden die Zaehler, die ohnehin schon danebenstanden. Dabei fiel auf, dass die Erfolgsmeldung “Alle Content-Visibility-Signale sind in Ordnung” abscore >= 80erschien, also auch bei bis zu einem Fuenftel Problemsignalen; sie haengt jetzt anproblem_count == 0. Eine Ebene tiefer traegtSignalCategorypassed/totalstattscore_pct, und der Tabellentitel nennt “17 von 24 Signalen erfuellt” statt eines mit sechs erfundenen Faktoren gewichteten Prozentwerts. - Abgeleitete Indikatoren als solche ausweisen (D3, schliesst Plan 42): Ein Leser sah SEO,
KI-Sichtbarkeit, Quellenqualitaet, UX und Journey als fuenf getrennte Bewertungen, die zufaellig
uebereinstimmen.
AiSignalKind::TechnicalTrustfaltetea11y_score >= 80undsecurity_score >= 70in die Zitierbarkeit,QualitySignalKind::Accessibilityzaehlte den Accessibility-Score noch einmal in der Authority-Dimension – beide entfernt. Die elf erfundenen Citation-Gewichte (0.08, 0.15, 0.10, …) suggerierten eine Kalibrierung, die es nicht gibt; jetzt gleichgewichtet unter benanntemSIGNAL_WEIGHT. Was bleibt, wird deklariert: neues Feldderived_from, gespeist austaxonomy::module_derived_from, plus ein Satz im PDF unter jedem betroffenen Indikator. Der Accessibility-Abzug auf UX und Journey wird beziffert statt still eingerechnet. - Die Ueberschrift ist die Barrierefreiheit (D1, schliesst Plan 41 §3): Note und Zertifikat
hingen am gewichteten Gesamtwert (#233, damit sie einander nicht widersprechen). Das loeste den
internen Widerspruch, rueckte die Kennzahl aber vom Gegenstand des Berichts weg:
inros-lackner.de mit Barrierefreiheit 20 erschien auf dem Deckblatt als 45, weil Performance,
Mobile und SEO sie hochzogen; casoon.de mit Barrierefreiheit 100 als 92. Beide lesen jetzt den
Barrierefreiheits-Score, koennen sich also weiterhin nicht widersprechen. Der gewichtete Wert
bleibt als benannte ScoreCard “Kombinierter technischer Wert” mit Gewichtsbasis – ohne sie
waere er aus dem PDF verschwunden, er stand nur auf dem Deckblatt.
report-lintprueft gegenaccessibility_score; neue Fixture, weil in allen bisherigen beide Scores gleich sind und keine den Wechsel bemerkt haette. Nebenbei zwei weitere Bandtabellen beseitigt: der Batch-Builder rechnete Note und Zertifikat mit eigenen Literalen (95/90/85/80/70/60), das Batch-Deckblatt mitBATCH_GRADE– derselbe Score war also je nach Bericht anders benotet. Beide nutzen jetzt dieselben zwei Funktionen wie der Single-Report;BATCH_GRADEentfaellt.
Verifiziert an frischen Reports von www.casoon.de (Barrierefreiheit 100, kombiniert 92, Note A) und www.inros-lackner.de (Barrierefreiheit 20, kombiniert 45, Note F, Zertifikat NICHT BESTANDEN);
report-lintauf beiden ohne Befund. - Note nur fuer Module, die den Gesamtwert tragen (D2): Sieben der dreizehn Module haben
-
Report-Qualitaetsdurchlauf, 2026-09-19: Ergebnis eines
report-critic-Laufs ueber frische Single-URL-Reports von www.casoon.de und www.inros-lackner.de.auditmysite report-lintwar auf beiden Reports ohne Befund — alle Punkte sind semantischer Natur und fuer den deterministischen Linter strukturell unsichtbar. Sechs Commits, je einer pro Befund.- Erklaerungs-Lookup uebersprang die axe-Kennung: JSON-Writer und Batch-Report loesten die
kundenseitige Erklaerung ueber die Taxonomie-
rule_idauf,get_explanationfaellt von dort auf die blosse WCAG-Nummer zurueck, und die regelspezifischen Eintraege haengen an der axe-Kennung. Befund: 4 von 15detail.fix_guidance-Eintraegen trugen fremden Text — die drei Landmark-Regeln erschienen als “Missing semantic structure” mit Tabellen-Markup als Codebeispiel. Das Single-PDF war korrekt, nur das JSON nicht. Dieselbe Fehlerklasse wie #571. Neuresolve_explanationals einziger Aufloeser an allen drei Aufrufstellen. Beim Fixen fiel auf, dass die nun erreichbaren Codebeispiele deutschen Text ins kanonisch englische JSON gebracht haetten; der Guard prueft jetzt nicht mehr nur auf Umlaute. accessibility_score_breakdownwar ein zweites Score-Modell: eigene Severity-Gewichte (20/14/8/4), Deckel bei 90, erfundene Wichtigkeits-Gewichte. Befund: einaccessibility_scorevon 20 neben Bereichen, deren gewichteter Schnitt 57 ergab. Ein gewichteter Schnitt kann den Score nicht reproduzieren —diversity_factor, sqrt-Kompression,apply_soft_floorund der Cap sind nicht-linear und haengen am gesamten Befundsatz. Die zwei vermischten Fragen sind jetzt zwei Felder:scoreist der Bereich allein durch den Scorer (neuscore_from_penalties),estimated_lost_pointsder Anteil am tatsaechlichen Verlust.sum(estimated_lost_points) == 100 - accessibility_scoregilt exakt und wird vonreport-lintgeprueft. Schema unveraendert.- Score-Bereiche kamen aus Fliesstext:
score_area_for_findingsuchte Woerter inrule_id + title + description + subcategory. Befund:a11y.landmark_region.missinglandete unter “Images / alternative text”, weil seine Beschreibungrole 'image'erwaehnt;seo.headings.multiple_h1trieb einen Bereich des Accessibility-Breakdowns. Neutaxonomy::score_areamit einer totalenrule_id -> ScoreArea-Tabelle ueber alle 120 Accessibility-Regeln und drei Inventar-Tests.score_area_for_key_pointwar eine zweite, wortgleiche Kopie derselben Suche und delegiert jetzt. - “Erwartungswert fuer diesen Seitentyp” war ein Literal:
intent_fit_scoreist eine von elf Konstanten und wurde gegenseo.scoreverglichen — andere Funktion, andere Skala. FuerThinContent(28) kippte die Aussage: eine duenne Seite “erfuellte die Erwartung”. Der Vergleich ist gestrichen; die Zahl bleibt als Qualitaetswert erhalten. Zusaetzlich haengt das Gateintent_fit_score < 65fuer drei Seitentypen an einer reinen Typ-Konstante, eine Media-Heavy-Seite bekam den Ratschlag also unabhaengig von ihrer Qualitaet. - Zwei Modelle fuer dieselben Risiken:
build_management_risksfuellte das JSON,compute_dimension_rowsbaute daneben das PDF-Panel. Befund: das JSON fuehrte 4x high, das Panel zeigte 3x bad und 2x warn, zwei high-Dimensionen fehlten ganz, und alle belegkraeftigen Begruendungen wurden verworfen. Neuaudit::management_riskals einzige Ableitung nach dem #406-Muster. Dabei fielen drei Aussagen ohne Messgrundlage weg: die Conversion-Ketten (die ohnehin kein Ausgabeformat erreichten), der konstante Trust-Satz und der Template-Schluss aus einer einzelnen Seite.RiskTier::Unknownist neu — ein nicht ausgefuehrtes Modul wurde vorher perunwrap_or(100)als Staerke gerendert. - Security behauptete Unauffaelligkeit ueber offenen Findings: Clean-Hinweis und Takeaway
hingen am Score, nicht an der Befundliste, und
derive_security_recommendationsprueft nur auf fehlende Header — ein vorhandener, aber fehlkonfigurierter Header erzeugte nichts, und der Default behauptete, die Header seien sauber gesetzt. Befund: casoon.de, Score 95, drei offene CSP-Findings (eines High), und die Sektion las “Sehr gut — keine wesentlichen Sicherheitsauffaelligkeiten”. Die Bandkorrektur ist jetzt symmetrisch, die Empfehlungen haengen anSecurityIssueKind, und die ScoreCard nimmt dasselbe korrigierte Band — sonst stand “95 — Sehr gut” ueber einem Takeaway “Verbesserungswuerdig”. Derselbe Defekt steckte in der SEO-Sektion.
- Erklaerungs-Lookup uebersprang die axe-Kennung: JSON-Writer und Batch-Report loesten die
kundenseitige Erklaerung ueber die Taxonomie-
-
Heuristik-Hygiene: keine stillen Abzuege mehr, 2026-09-19: Plan 43, Teile 1 und 2.
- Abzuege ohne genannten Grund: In
ux/analysis.rssenkten drei Pruefungen den Score voellig stumm (Wortzahl 50-100, lange Seite mit unter fuenf Ueberschriften, unter drei Vertrauens-Stichworten), vier weitere zogen ab dem ersten Vorkommen ab, meldeten aber erst ab einer deutlich hoeheren Schwelle. Befund:reports/inros-lackner-audit.jsonwiescontent_clarity: 80aus, ohne dass irgendein ContentClarity-Befund in der Liste stand — 80 ist genau ein 20-Punkte-Abzug, der stille Wortzahl-Zweig. Die Regel stand schon im Code: derLargeDom-Zweig traegt den Kommentar, der Score duerfe nie ohne genannten Grund fallen — angewendet war sie an genau einer Stelle. Jetzt meldet jeder Abzug; damit das nicht von stumm auf alarmierend kippt, istux_issue_severitywertabhaengig: milde Baender meldenlow. Injourney/analysis.rshatten fuenf Abzuege keinen Friction-Point, dafuer gibt es fuenf neueFrictionKind-Varianten. - Binaere Schwellen neben der Prozentzahl, die sie ignorieren: Die
Cross-Page-Konsistenzsignale waren Booleans, waehrend der Report den echten Prozentwert
ausgab — 94 % HSTS-Abdeckung zaehlte exakt wie 0 %, sichtbar neben einer “94 %”.
QualitySignalhat jetzt einfulfilment(0.0-1.0);presentbleibt die Pass/Fail-Markierung, gerechnet wird proportional.ErrorFreePagesbenutzt dasclean_pct, das es ohnehin schon berechnet und verworfen hatte. Echte Ja/Nein-Signale bleiben unveraendert. - Dabei gefunden (Plan 49):
AXTreeiteriert ueber eineHashMap, und die Heading-Skip-Erkennung liest das trotzdem — zwei Laeufe derselben Seite lieferten unterschiedliche Befunde. Ausserdem zaehlteanalyze_content_clarityText ueberiter()und fuehrteStaticTextals erste Rolle auf, obwohliter()genau diese Rolle herausfiltert: der Zweig konnte nie greifen, und der Abzug war ein Falschbefund. Mititer_all()geht inros-lackner.de voncontent_clarity80 auf 100. Hier behoben, weil sonst aus einem stillen Falschbefund ein sichtbarer geworden waere.
- Abzuege ohne genannten Grund: In
-
Verdict, Einstufung und Herleitung des Gesamtwerts, 2026-09-19: Fortsetzung des Report-Qualitaetsdurchlaufs, Plan 34 zusammen mit Plan 44.
- Der Gesamtwert war nicht herleitbar: Die Tabelle “Warum ist der Gesamtwert, was er ist”
listete die Module mit ihren Gewichten; nachgerechnet ergab das 90.05 gegen ausgewiesene 92
(casoon) und 47.05 gegen 45 (inros-lackner). Befund: es gibt zwei Rechenwege, und nur einer
ist die Modulgewichtung. Im
viewport_weighted-Modus entsteht der Wert aus einer Desktop/Mobile-Mischung mit eingemischter Security — das steht inscore_breakdownund wurde nirgends gerendert. Die Tabelle heisst dort jetzt “Die gewichteten Module im Einzelnen”, eine zweite Tabelle zeigt die echte Rechnung und reproduziert den Wert exakt. Immodule_weighted-Modus bleibt alles, wie es war — dort ist die Gewichtung die Herleitung. - Verdict und Einstufung fehlten im PDF vollstaendig:
summary.certificatewurde nur als Gate gelesen,verdict/verdict_reasonsgar nicht gerendert. DasSummaryBlock-Feld namensverdictist ein Prosa-Satz, ein anderer Wert unter gleichem Namen. Befund: die CLI druckte “FAIL — legal_flags: 5, blocking_issues: 36”, im Dokument stand davon nichts, und das Deckblatt las “Ausbaufaehiger technischer Zustand”. Entschieden: das PDF nennt beides, das Label heisst “Gesamteinstufung” statt “Zertifikat” —calculate_certificatebildet den Score auf ein Bandlabel ab und zertifiziert nichts. Ein Teil-Lauf darf eine Einstufung tragen, aber als vorlaeufige, und das steht jetzt vorn statt nur im Anhang. - Die Verdict-Gruende waren Maschinen-Tokens:
legal_flags: 5unveraendert zu rendern haette kanonisches Englisch ins deutsche PDF gebracht.VerdictReasonKindtraegt jetzt die Zahlen,text(en)ist die einzige leserseitige Quelle;VerdictResult::reasonsbehaelt seine exakten Strings fuer JSON und CLI,reason_kindsistserde(skip).
- Der Gesamtwert war nicht herleitbar: Die Tabelle “Warum ist der Gesamtwert, was er ist”
listete die Module mit ihren Gewichten; nachgerechnet ergab das 90.05 gegen ausgewiesene 92
(casoon) und 47.05 gegen 45 (inros-lackner). Befund: es gibt zwei Rechenwege, und nur einer
ist die Modulgewichtung. Im
-
Umstellung auf die geteilten a11y-core-Crates, zweiter Abschnitt, 2026-09-19: Alle drei Crates von 0.2.0 auf 0.3.0. Damit fällt der Blocker des ersten Abschnitts weg:
RuleRunführt jetztviewportundwcag, undFindinghat mitrule_name,with_element(role, name)und dem undurchsichtigenExtra-Slot die Felder, an denen die Umstellung vonViolationzuvor scheiterte.-
RuleOutcomedurcha11y_report::RuleRunersetzt: Der Ausführungsvermerk kommt aus dem geteilten Crate. Der Schlüssel eines Vermerks ist damit(rule_id, viewport)— dieselbe Regel läuft je Viewport einmal, undrule_runsundfindingsbenutzen dieselbe Namensmenge. Befund: Von den sieben Werten der lokalenRuleOutcomeStatussagten vier dasselbe aus — dass die Regel gelaufen ist. Kein Auswerter im Code hatViolationsFound,Warning,ManualReviewRequiredundNoViolationDetectedje unterschieden; gelesen wurden ausschließlichFailedundSkipped. Die vier waren schreibend tot. Dass sie zusammenfallen, ist kein Verlust, sondern der Zwei-Achsen-Schnitt: wie sicher eine Aussage ist, steht am Befund (Outcome), nicht am Vermerk. JSON ändert sich inpages[].detail.rule_outcomes[]:statusentfällt (not_runsagt jetzt, ob und warum eine Regel nicht lief),wcag_criterion→wcag(Array statt Einzelwert),reason_code→reason,finding_count→findings. -
Die Übersetzungsschicht in
wcag::sharedfällt weg: Beide Seiten sprechen dasselbe Modell, der Vermerk ausa11y-ruleswird durchgereicht statt übersetzt. Ergänzt wird nur das WCAG-Kriterium, das der geteilte Bestand am Vermerk nicht mitführt. -
Zwei weitere Regeln abgelöst und gelöscht, nicht danebengestellt:
parsing::check_parsing_with_page(axe-Kennungduplicate-id) läuft jetzt alsids/duplicate,focus_order::check_positive_tabindex_with_pagealskeyboard/positive-tabindex. Beide lasen den DOM per JavaScript, weil wederidnochtabindexAX-Eigenschaften sind (#QA-030); die geteilten Regeln lesen dieselben Attribute aus dem CDP-Abzug. JSON ändert sich:rule_idist für diese Befundeids/duplicatebzw.keyboard/positive-tabindex. Severity sinkt in beiden Fällen vonHighauf dasMediumdes geteilten Bestands — das wirkt auf die Bewertung und ist bewusst nicht lokal überschrieben, weil ein eigener Schweregrad die Zusicherung bräche, dass derselbe Befund überall gleich heißt. -
Was danebensteht und warum:
parsing::check_parsing(duplicate-id-aria, widersprüchlichearia-owns-Beziehungen im AX-Baum) undfocus_order::check_focus_order(fokussierbar trotzaria-hidden) bleiben — sie prüfen etwas anderes als die abgelösten Hälften.iframe_ruleserzeugt weiterhinduplicate-id, aber für das Dokument im iframe. -
Befund an der eigenen Testabdeckung: Die Kennungsänderung an
keyboard/positive-tabindexschlug inpatterns_disclosure.expected.jsondurch — dieser Korpusfall erwartetefocus-order-semantics, ausgelöst durch dastabindex="5"in seinem Fixture, nicht durch die ARIA-Hälfte derselben Kennung. Zwei Regeln hinter einer axe-Kennung lassen sich am Korpus also nicht auseinanderhalten. -
Befund an
docs/OUTPUT_CONTRACT.md: Der Vertrag behauptete,rule_outcomesbenutze dieselben Ausführungszustände wiemodule_runs(completed,partial,failed,skipped,not_applicable). Das stimmte schon vor dieser Umstellung nicht —RuleOutcomeStatusschriebviolations_found,no_violation_detected,warning,manual_review_required. Aufgefallen ist es erst, weildocs/json-report.schema.jsonrule_outcomesals unbeschränktes{"type": "array"}führt und die Form der Einträge gar nicht prüft. Der Vertragstext ist nachgezogen. -
Noch offen:
Violationist weiterhin auditmysites eigener Typ und nichta11y_report::Finding— der Umbau ist jetzt zwar möglich, aber mit 295 Konstruktionsstellen und 155 betroffenen Dateien zu groß für diesen Abschnitt. Von 24 geteilten Kennungen sind jetzt 4 übernommen (document/lang-missing,document/lang-invalid,ids/duplicate,keyboard/positive-tabindex), 20 stehen aus. -
Warum nicht mehr Regeln: Ein Fähigkeitsvergleich Regel für Regel zeigt ein systematisches Muster statt einzelner Lücken: auditmysites Regeln entscheiden über Chromes berechnetem Accessibility-Tree, die geteilten über Tags und Attribute. Wo beide dasselbe Kriterium bedienen, ist die AX-basierte Fassung deshalb meist die breitere. Drei belegte Fälle:
lists/invalid-structureprüft nur<ul>/<ol>mit fremden Kindern, währendrules::list_structurezusätzlich leere Listen und<dt>ohne<dd>kennt und über Rollen auchrole="list"auf einem<div>erfasst;tables/header-missingsucht<th>, währendrules::table_rulescolumnheader/rowheaderals Rollen sucht und damit auch ARIA-ausgezeichnete Kopfzellen findet;zoom/viewport-lockedvergleicht dencontent-Wert ohne vorheriges Kleinschreiben und übersieht damituser-scalable=NO, und es kennt die Abstufung nicht, mit derrules::resize_textuser-scalable=no(High) vonmaximum-scale<2(Medium) trennt. Diese drei sind Kandidaten dafür, den geteilten Bestand zu stärken statt auditmysite zu schwächen — das gehört aber in einen eigenen Pull Request in a11y-core und muss dort veröffentlicht sein, bevor auditmysite es nutzen kann. -
Die drei Lücken sind in a11y-core behoben, aber noch nicht veröffentlicht: Ein eigener Pull Request dort (Branch
feat/staerkere-strukturregeln, vorgesehen als 0.4.0) schreibt dencontent-Wert des Viewports vor dem Vergleich klein, lässtrole="list"/role="listitem"undrole="columnheader"/role="rowheader"/role="table"gleichberechtigt neben den Tags gelten und ergänzt die Kennunglists/empty. Solange das nicht auf crates.io steht, kann auditmysite es nicht nutzen — die drei Regeln bleiben bis dahin auditmysite-eigen.
Verifiziert:
cargo clippy --all-targetsohne Befund;cargo test --lib1404 Tests undcargo test --testsalle browserfreien Binaries grün. Diesmal mit Chrome gelaufen, was der erste Abschnitt schuldig blieb:cargo test --test detection_corpus_test -- --ignoredgrün (218 s) — dort zeigt sich, dass beide Kennungsänderungen wirklich tragen, und genau dort fiel die Fixture-Lücke beipatterns_disclosureauf.cargo test --test integration_test -- --ignoredliefert 22 bestanden, 1 fehlgeschlagen — derselbe Stand wie aufmain, gegen den eigens gegengeprüft wurde:test_concurrent_wait_for_stable_stays_within_its_timeout_budgetfällt auch ohne diese Änderungen durch, sobald die volle Suite nebenläufig läuft. Es ist ein lastabhängiger Zeitbudget-Test, kein Regressionsbefund — aber ein eigener, bisher nicht vermerkter. -
-
Umstellung auf die geteilten a11y-core-Crates, erster Abschnitt, 2026-09-19: auditmysite bezieht Befundmodell und einen ersten Teil des Regelbestands aus a11y-core (alle Crates 0.2.0), damit derselbe Befund in astro-post-audit, auditmysite und liveaudit dieselbe Kennung trägt. Bewusst schrittweise: Dieser Stand stellt das Modell und eine Regel um, nicht den ganzen Bestand.
- Doppelte Typdefinitionen entfernt:
SeverityundViolationEvidencewaren strukturell identisch zua11y_report::Severitybzw.a11y_report::Evidence— gleiche Varianten, gleiche Reihenfolge, gleiche serde-Darstellung. Beide sind durch die geteilten Typen ersetzt; die auditmysite-eigenen Zusätze anSeverity(deutsche Labels, Umsetzer aus den Alt-Systemen) hängen alsSeverityExtdaran, weil an einem fremden Typ keine inhärenten Methoden ergänzt werden können. Das frühereDisplay for Severityfällt der Orphan-Regel zum Opfer; die eine Fundstelle nutztas_str(), das exakt dieselben Strings liefert. JSON unverändert. FindingKinddurcha11y_report::Outcomeersetzt: Variante für Variante dasselbe Enum unter anderem Namen. JSON ändert sich: Das Feldkindschreibt jetzt"fail"/"review"/"pass"/"untested"statt"violation"/"warning"/"positive"/"not_testable". Kein Fixture und kein Snapshot beobachtete das Feld — die Änderung wäre unbemerkt geblieben, deshalb pinnt ein neuer Test die Vokabel jetzt ausdrücklich.- CDP-DOM als
a11y-dom-Dokument (accessibility::dom_document): Die geteilten Regeln sind generisch über ein bewusst DOM-förmiges Trait, weil die Mehrzahl von ihnen über Tags und Attribute entscheidet (lang,tabindex,id,alt). Der AXTree gibt die gar nicht her. Neu wird der DOM einmal perDOM.getDocumentgeholt und in eine Arena materialisiert;Semanticswird über Chromes nativen Accessibility-Tree erfüllt, nicht überaccname— das ist der Ersatz für Hosts ohne nativen Tree. Beide Bäume finden über die Backend-Node-ID zueinander. Wurzel ist<html>(die Regeln prüfendoc.root()), Shadow-Root-Kinder hängen unter ihrem Host, iframe-Inhalte werden nicht betreten (eigeneslang/title). - 3.1.1 läuft als geteilte Regel, die eigene ist gelöscht: auditmysite prüfte 3.1.1 zweimal
und beide Male schlecht.
rules::language::check_languagelas die AX-Eigenschaftlanguage, die Chrome aber aus Locale und Kontext synthetisiert, auch wenn der Autor nie einlanggesetzt hat — für den häufigsten Verstoß also blind. Ausgeglichen wurde das durchpipeline::apply_lang_attribute_check, eine zweite, per JavaScript nachgeschobene Prüfung, die den Befund der ersten zurücknahm oder nachtrug. Ungültige Sprachcodes kannte keine von beiden. Beide sind gelöscht; 3.1.1 läuft jetzt alsdocument/lang-missing/document/lang-invaliddirekt gegen das DOM-Attribut. JSON ändert sich:rule_idist für diesen Befunddocument/lang-missingstatthtml-has-lang.html-has-langbleibt bestehen, wird aber nur noch voniframe_ruleserzeugt — für das Dokument im iframe, ein anderer Befund. - „Nicht gelaufen” ist nicht „bestanden”: Die geteilten Kennungen, die auditmysite noch nicht
führt, werden je Kennung als
shared_rule_not_yet_adoptedvermerkt statt stillschweigend verworfen. Fällt der DOM-Abruf aus, werden sie alsFailedvermerkt, nicht als bestanden. - Noch offen:
Violationist weiterhin auditmysites eigener Typ und nichta11y_report::Finding(es fehlen dort Felder, die auditmysite pro Befund mitführt:rule_name,role,name,evidence_screenshot,evidence_viewport);RuleOutcomeist nicht aufRuleRunumgestellt; von 24 geteilten Kennungen ist eine Regel mit 2 Kennungen übernommen.
Verifiziert:
cargo clippy --all-features --all-targetsohne Befund; 1667 Tests in 18 browserfreien Testbinaries bestanden, 0 fehlgeschlagen. Die browsergestützten Korpus-Tests (detection_corpus_test) brauchen Chrome und liefen dabei nicht. - Doppelte Typdefinitionen entfernt:
-
a11y-core 0.6.0, 2026-09-19 — drei Regeln abgelöst:
list_structure,table_rulesundmeta_viewport_largesind gelöscht; ihre Prüfungen kommen jetzt aus dem geteilten Bestand.SHARED_RULESführt damit 12 statt 4 Kennungen.Möglich wurde das erst, nachdem a11y-core die Lücken geschlossen hat, an denen die Ablösung vorher gescheitert wäre: Begriff ohne Definition, präsentationale Tabelle mit Kopfzellen, Tabellenname, die Trennung von Zoom-Verstoß und Zoom-Begrenzung — und beim Abräumen fiel ein vierter auf, der verwaiste Listeneintrag (
<li>ohne Liste darüber). Die alte Regel lief über den AX-Tree und sah ihn; die geteilte lief über Listen und hätte ihn nie besucht. Ohnelists/item-outside-listin 0.6.0 hätte die Ablösung eine Prüfung verloren statt sie zu teilen.Eine bewusste Abweichung: Die fehlende Tabellenbenennung ist im geteilten Bestand
needs_review, nichtviolation— ob eine Tabelle einen Namen braucht, hängt vom Kontext ab, und ein Verstoß wäre eine Behauptung, die die Regel nicht decken kann. Das Korpus hält das fest.meta_viewport_largeverschwindet ausPAGE_RULES(47 → 46); die dort liegenden Viewport-Helfer sind nachresize_text.rsgewandert, das als einzige Nutzerin bleibt und über die 200-%-Schwelle hinaus die Textvergrößerung selbst prüft.Verifiziert: 1.430 browserfreie Tests, 27 Testbinaries, clippy
-D warningsundfmtsauber, Browser-Korpus mit echtem Chrome (217 s). Der Vollständigkeitstest des Korpus war der Wächter, der die drei verbliebenen Fixture-Verweise auf die alten Kennungen gefunden hat. -
a11y-core 0.4.0, 2026-09-19 — was noch nicht ablösbar ist: Mit 0.4.0 lesen die geteilten Strukturregeln auch
role-Attribute (role="list",role="columnheader",role="table"), undpositive-tabindexsteht wieder aufHigh. Drei naheliegende Ablösungen bleiben trotzdem aus — sie wären ein Fähigkeitsverlust, nicht ein Tausch:- Listen:
lists/invalid-structureundlists/emptydecken zwei der drei Prüfungen ausrules/list_structure.rsab. Es fehlt, ob ein<dt>eine zugehörige Definition hat. - Tabellen:
tables/header-missingdeckt nur die Kopfzellen ab.rules/table_rules.rsprüft zusätzlich Caption bzw. Accessible Name und ob präsentationale Tabellen fälschlich Kopfzellen führen. - Viewport:
zoom/viewport-lockedprüftmaximum-scale < 2.0und entspricht damitrules/resize_text.rs(1.4.4, 200 %) — nichtrules/meta_viewport_large.rs, das dieselbe Auszeichnung bei der strengeren 500-%-Schwelle prüft. Eine Ablösung vonmeta_viewport_largewäre die falsche Zuordnung gewesen.
Verifiziert: Bau und 1.442 browserfreie Tests gegen 0.4.0, clippy
-D warningssauber. Die drei Lücken gehören in die nächste a11y-core-Runde, nicht in eine lokale Sonderlocke. - Listen:
-
1.5.0, 2026-09-19 — Lizenzwechsel auf MIT: auditmysite steht ab dieser Version unter der MIT-Lizenz. Frühere Releases bleiben unter der Lizenz, die zum jeweiligen Zeitpunkt galt — bis 0.25.x AGPL-3.0-or-later, 0.26.0 bis 1.4.0 Business Source License 1.1; das ist in
NOTICEfestgehalten und muss nicht rückwirkend geändert werden.Warum: Der Regelbestand wandert nach a11y-core, damit derselbe Befund in astro-post-audit (Build-Zeit), auditmysite (CI/Crawl) und liveaudit (laufende Seite) gleich heißt. Läge auditmysite weiter unter BUSL, müsste bei jedem Modul, das dorthin wandert, einzeln über die Lizenz entschieden werden — eine Abgrenzung, die dauerhaft gepflegt werden müsste und quer zum Ziel steht.
Unumkehrbar: Ein einmal unter MIT veröffentlichter Release lässt sich nicht zurückholen. Bewusst so entschieden.
-
1.4.0, 2026-09-18 — Upgrade-Hinweise: Kein reines Patch-Release. Was Konsumenten des JSON-Reports und der Bibliothek beim Hochziehen prüfen sollten (Details jeweils in den Einträgen unten):
- Gesamtscores verschieben sich: HTML-Conformance zählt seit dem Per-Ursache-Scoring-Fix mit
5 % in den Gesamtscore, SEO sinkt von 20 % auf 15 %. Reports derselben Seite ergeben andere
overall_score-Werte als unter 1.3.x. - Geänderte JSON-Werte (nicht nur ergänzte Felder):
rich_snippets_potentialnennt den bloßen Schema-Typ ("FAQPage"statt"FAQ Rich Snippet"); die Schlüssel inmobile.issues[].values.small_by_contextsind kanonisch englisch (other/formstattsonstige/formular). - Zwei SEO-Befunde entfallen:
pagination_missing_rel_linksundpwa_missing_service_workerwerden nicht mehr emittiert. - Additiv:
security.issues[].valuesundmobile.issues[].valuestragen die interpolierten Rohwerte; leer werden sie weggelassen.header,issue_typeundmessagebleiben unverändert. - Rust-API bricht:
security::coop_corp_verification_textist entfallen (insecurity_issue_textaufgegangen),SecurityIssueundMobileIssuehaben ein neues öffentliches Feldvalues, unda11y_journey::link_inventory::analysenimmt jetzt Seiten- und Fallback-Sprache statt einer Locale. - Bekannter Konsument: Studio hing auf 1.3.1 (siehe Auto-Memory) — vor dem Upgrade prüfen.
- Gesamtscores verschieben sich: HTML-Conformance zählt seit dem Per-Ursache-Scoring-Fix mit
5 % in den Gesamtscore, SEO sinkt von 20 % auf 15 %. Reports derselben Seite ergeben andere
-
Report-Aussagen: Widersprüche und Überbehauptungen behoben, 2026-09-18: Ausgelöst durch eine externe Kritik am casoon.de-Report. Der Report behauptete an mehreren Stellen mehr, als die Messung hergibt, und widersprach sich dabei selbst.
- Linktext-Heuristik erzeugte falsche WCAG-2.4.4-Behauptungen:
a11y_journey::link_inventorysis_genericverglich perlower.contains(stopword), also als Substring. Damit galt jeder beschreibende Linktext als generisch, der irgendwo ein Stoppwort enthielt — an casoon.de „Claude-Code-Kontingent: Der Verlauf kostet mehr als jede Konfiguration” (200 Zeichen Kartentext), „Mehr über Jörn Seidel”, „Mehr Sichtbarkeit bei Google & KI”; die englische Liste traf überhere/more/openzusätzlich mitten in deutsche Wörter. Alle vier gemeldeten Links waren False Positives. Jetzt exakter Vergleich auf dem normalisierten Namen (lowercase, umschließende Nicht-Alphanumerik entfernt, sodass „Mehr erfahren »” weiterhin trifft). Zusätzlich folgt die Detektionssprache jetzt der Seiten-Sprache (<html lang>, pereval_stringim Journey-Hook gelesen) statt der Lauf-Sprache, mit expliziterSTOPWORD_LOCALES-Prüfung —I18n::newliefert für unbekannte Locales stillschweigend das deutsche Bundle, eine französische Seite wäre sonst mit deutschen Stoppwörtern gescannt worden (#406-Trennung Detektions- vs. Message-Sprache). Vier Regressionstests, darunter die drei echten casoon.de-Namen. Der Befundtext behauptet außerdem nicht mehr „erfüllen WCAG 2.4.4 nicht”, sondern kennzeichnet sich als heuristische Warnung mit manuellem Prüfbedarf. - Drei Evidenzklassen statt einer: Neue
EvidenceClasses(bestätigte WCAG-Verstöße / heuristische Warnungen / manuell zu prüfende Kriterien) auf demFindingsBlock, einmal im Builder berechnet. Scores bleiben unverändert — sie werden weiterhin allein aus den bestätigten Verstößen abgeleitet. Die Klassen existieren, damit kein Zählsatz mehr „0 Befunde” sagen kann, während vier Seiten später Befunde stehen: Die Ursachenanalyse sagt jetzt „Keine bestätigten WCAG-Verstöße … Es bleiben N Warnung(en) und M Prüfhinweis(e)” statt „Es wurden keine Barrierefreiheits-Befunde erkannt”, und unter dem Zählstrip der Management-Sicht steht explizit, dass dort ausschließlich bestätigte Verstöße gezählt werden. Der „Heuristische Warnungen”-Metrikwert zählt jetzt auch die Journey- und Screenreader-Signale, nicht mehr nur die Warnungen der WCAG-Engine. - BFSG-Aussagen auf technische Kriterien zurückgeführt: „BFSG-Prüfung: keine Verstöße im geprüften Umfang” (grün) und die Checklisten-Zeile „Rechtliche Konformität (BFSG) — Geringes Compliance-Risiko” lasen sich wie eine rechtliche Freigabe. Ein Scanner kann weder feststellen, ob ein Dienst überhaupt unter das BFSG fällt, noch ob sämtliche gesetzlichen Anforderungen erfüllt sind. Beide heißen jetzt „BFSG-relevante technische Kriterien” und sagen explizit „Dies ist keine rechtliche Konformitätsprüfung.”
- Breadcrumb-Widerspruch (
seo::serp): Das SERP-Signal prüfte nur die Typ-Präsenz vonBreadcrumbListund meldete dann „Google zeigt Pfadangabe im Listeneintrag” — während die Schema-Tabelle desselben Reports „Pflichtangaben fehlen: itemListElement (minimum 2 entries)” ausgab. Das Signal konsultiert jetzt die bereits vorhandenenrule_assessments; ein unvollständiges Schema wird zur Warnung mit Nennung der fehlenden Felder. Zwei Regressionstests. - Maßnahmenliste modulübergreifend: „Die 5 wichtigsten Maßnahmen” wurde ausschließlich aus
WCAG-Findinggruppen gespeist und meldete bei 0 WCAG-Befunden „Keine dringenden Maßnahmen
erforderlich” — obwohl derselbe Report Duplicate Host, fehlerhaftes Breadcrumb-Schema, 13.756
DOM-Knoten und eine CSP-Fehlkonfiguration dokumentierte. Neu füllt
cross_module_measuresdie Liste aus Security-, Mobile-, SEO- und Performance-Empfehlungen auf, sortiert nach Schwere und danach nach den Overall-Punkten, die das Modul kostet. Der Leertext des Maßnahmenplans verneint keine Befunde mehr, sondern verweist auf die Modulabschnitte. - Terminologie und PDF-Artefakte: TBT wird nicht mehr als Core Web Vital ausgewiesen
(
perf-lab-data-bodynennt LCP/CLS als CWV und TBT als Lab-Näherung, weil INP headless nicht messbar ist); die Abschnitte „Desktop/Mobile — Core Web Vitals” heißen „Kennzahlen (Labormessung)“, weil ihre Strips auch FCP und TTFB enthalten, die Zusatztabelle heißt „Weitere Lab-Metriken”. Die Schwellwerte für DOM-Knoten, Ladezeit und DOMContentLoaded sind als „Richtwert” statt „Ziel” beschriftet (Lighthouse-Heuristik, keine Norm). Interne Journey-IDs (tab_walk,disclosure_0,skip_link_0) erscheinen nicht mehr roh im Kunden-PDF, sondern als Labels, mit Rückfall auf die rohe ID bei unbekannten Journeys. Der Callout über den interaktiven Befunden ist nicht mehr grün (der Erfolgszweig war ohnehin unerreichbar, weil der Abschnitt nur bei vorhandenen Befunden gerendert wird) und ist grammatikalisch korrekt im Singular.rich_snippet_typeheißt nicht mehr „FAQ Rich Snippet”, sondern nennt den bloßen Schema-Typ — vorhandenes Markup ist keine Zusage über die Suchdarstellung. Das Gesamturteil sagt „im automatisierten Accessibility-Prüfumfang” statt „im Accessibility-Audit”. - Verifiziert:
cargo test --all-features(1376 Lib- plus Integrationstests grün),cargo clippy --all-features --all-targetsohne Warnungen,cargo fmt --check, sowie ein echter Lauf gegen www.casoon.de — der Linktext-Befund ist verschwunden (2 → 1 interaktive Befunde), das Breadcrumb-Signal steht auf „Warnung” mit Begründung, die Maßnahmenliste führt fünf modulübergreifende Punkte, undreport-lintmeldet weiterhin keine Findings. Score unverändert (Accessibility 100, Overall 92). - Zwei fachlich fragwürdige SEO-Regeln entfernt (Nachtrag desselben Tages):
pagination_missing_rel_linksmeldete fehlendesrel="prev"/rel="next"-Markup als SEO-Problem — Google nutzt das Paar seit 2019 nicht mehr zur Indexierung. Die übrigen Pagination-Prüfungen bleiben: sie greifen nur, wenn rel-Links tatsächlich vorhanden und falsch sind (Selbstreferenz, prev == next), und das ist in jedem Fall kaputtes Markup.pwa_missing_service_workermeldete eine fehlende Service-Worker-Registrierung, sobald ein Web-App-Manifest vorhanden war — ein Manifest wird aber routinemäßig allein für Icons und Theme-Color ausgeliefert (an casoon.de exakt dieser Fall: gültiges Manifest,display: standalone, keine Registrierung) und belegt keine Absicht, eine installierbare, offline-fähige PWA zu sein. Dieservice_worker_*-Felder bleiben als Information im JSON. Beide Tests durch Negativtests ersetzt. Ergebnis an casoon.de: „Technische SEO-Probleme” schrumpft von drei auf eine Zeile (den echten Duplicate-Host-Befund). - Maßnahmenliste fasst gleichartige Befunde zusammen: Nach dem Entfernen der beiden
SEO-Regeln rückten drei separate CSP-Fehlkonfigurationen nach und belegten drei der fünf
Plätze.
cross_module_measuresfasst jetzt Befunde mit gleichem Modul und gleichem Titel zu einer Maßnahme zusammen und hängt „(+N weitere gleicher Art)” an — drei CSP-Befunde sind eine Maßnahme „CSP korrigieren”.TopMeasureträgt dafür Titel und Detail getrennt; Performance- und technische SEO-Empfehlungen haben keinen eigenen Titel und werden ohne Trenner gerendert. Drei Rendering-Tests. Dabei mitkorrigiert: die Mobile-Einträge trugen den rohen snake_case-Kategorieschlüssel (touch_targets,fonts) — sie nutzen jetzt dasselbemobile_category_label, das der Mobile-Modulabschnitt schon verwendet („Touch-Targets”, „Schriftgrößen”). - Security- und Mobile-Meldungen lokalisiert (Nachtrag desselben Tages, #406): Beide Module
backten englische Prosa in
messageund der PDF-Renderer gab sie unverändert im deutschen Report aus („CSP allows unsafe-inline scripts without nonce/hash protection”, „1 touch targets are too small”). Beide folgen jetzt dem kind-Enum-Muster:SecurityIssueKind(19 Varianten) bzw.MobileIssueKind(6) plus*IssueValuesfür die interpolierten Rohwerte, und je eine reinesecurity_issue_text/mobile_issue_textals EINZIGE Textquelle — die Analyse ruft sie miten=true, die PDF-Präsentationsschicht überlocalized_message()mit der Lauf-Sprache. Die Konstruktorensecurity_issue/mobile_issuebauenmessagedaraus ab, sodass an keiner Aufrufstelle mehr ein Meldungstext von Hand steht.kind()wird aus den bereits gespeichertenheader/issue_type-Feldern abgeleitet statt als viertes Feld serialisiert, damit Reports und gecachte Artefakte älterer Builds unverändert weiterlokalisieren; unbekannte Typen fallen auf die gespeicherte kanonische Fassung zurück. JSON-Vertrag bleibt identisch (header,issue_type,messageunverändert englisch;valuesist additiv und wird leer weggelassen), abgesichert durchsecurity_issue_types_match_the_stored_contract. Dabei mitkorrigiert: das Mobile-Seitenskript schrieb die deutschen Kontextschlüsselsonstige/formularins eigentlich englisch-kanonische JSON — jetztother/form, lokalisiert übermobile_context_label(unbekannte Schlüssel gehen unverändert durch). Die #578-Sonderlösungcoop_corp_verification_textist entfallen, sie ging insecurity_issue_textauf. Guard-Tests pro Modul (jede Variante lokalisiert, EN ohne Umlaute) plus Fallback- und Round-Trip-Tests. - Check-Inventar aus dem Enum statt aus dem Quelltext:
tests/common/nonwcag_rule_inventory.rsermittelte die kanonischen Security-Check-IDs, indem essrc/security/mod.rsnachSecurityIssue { header: "…", issue_type: "…" }-Stringliteralen durchsuchte — durch den Umbau standen dort keine Literale mehr und der Corpus-Test schlug fehl. Die IDs kommen jetzt aussecurity::all_security_check_ids(), abgeleitet ausSecurityIssueKind::ALL; der Textscan samt hand-gepflegterCSP_DYNAMIC_DIRECTIVE_IDS-Liste entfällt, und die CSP-Pflichtdirektiven stehen alsCSP_REQUIRED_DIRECTIVESan einer Stelle. - Prüfumfang aufs Deckblatt (Nachtrag desselben Tages): Die Scope-Zeile stand nur auf Seite 2,
das Deckblatt zeigte „Website-Qualitätsbericht · casoon.de · 92” und einen nackten
„Barrierefreiheit 100”-Gauge — wer dort aufhört, liest beides als Aussage über die ganze
Website. Die Zeile kommt jetzt aus einer gemeinsamen Quelle (
single_report::audit_scope_line) und steht im Cover-Untertitel; das Label über dem Gauge-Streifen lautet „MODULE · 0–100 IM AUTOMATISIERTEN PRÜFUMFANG · HÖHER IST BESSER”. - Dabei einen Layout-Bug erzeugt und behoben: Der erste Versuch hängte die Scope-Zeile an den
vollen Kicker an, der Untertitel umbrach auf zwei Zeilen und schob die Labels der unteren
Gauge-Reihe (Mobile, UX, Journey) aus der Seite — das Typst-Template gibt jedem Gauge-Label
eine feste 22pt-Box und die Coverseite wächst nicht, der Überlauf wurde still verworfen. Per
gerendertem PDF entdeckt, nicht im Typst-Quelltext sichtbar.
narrative-cover-kickerist deshalb jetzt knapp („Technischer Website-Check” statt „… mit Fokus auf Accessibility, SEO und Performance”) — die weggefallenen Bereiche benennt der Gauge-Streifen direkt darunter ohnehin. Neuer Wächtertest_cover_subtitle_stays_on_one_line(Zeichenbudget 95, gemessen: ~83 passen, 132 brachen um) plustest_cover_carries_scope_line_and_gauge_qualifier. - Bewusst nicht geändert: die Score-Inflation — siehe
plan/29-report-claim-integrity-followups.md.
- Linktext-Heuristik erzeugte falsche WCAG-2.4.4-Behauptungen:
-
Produktdokumentation: Widersprüche zum tatsächlichen Verhalten korrigiert, 2026-09-17: Drei bestätigte Abweichungen behoben. (1)
README.mds HTML5-Conformance-Beschreibung sagte noch “score-neutral”, obwohl das Modul seit dem Per-Ursache-Scoring-Fix vom 2026-09-16 (contributes_to_overall: true,weight_pct: 5) real in den Gesamtscore einfließt. (2)src/lib.rss Modulübersicht listete “HTML” als Report-Format, obwohl kein HTML-Export existiert. (3) Die Kurzbeschreibung sagte an mehreren Stellen (Cargo.toml,src/lib.rs,src/cli/args.rs,src/cli/plan.rss Laufzeit-CLI-Banner,CLAUDE.md,docs/ARCHITECTURE.md) pauschal “WCAG 2.1”, währendREADME.mdeinzelne WCAG-2.2-Regeln (2.4.11/2.4.12, 3.3.7, 2.5.8) bereits korrekt einzeln kennzeichnet — überall auf “WCAG 2.1 AA (+ select 2.2 criteria)” vereinheitlicht. Dabei einen weiteren, direkt benachbarten Fund mitkorrigiert:src/lib.rss “Comprehensive: Checks WCAG 2.1 Level A, AA, and AAA” widersprachREADME.mds eigener Aussage “AAA is not fully implemented yet” — jetzt präzise: 36 von 50 automatisierten A/AA-Kriterien (docs/PARITY_CONTRACT.jsonc), plus ausgewählte 2.2- und einzelne AAA-Kriterien.docs/browser-architecture.mds veraltetes Homebrew-Formula-Beispiel bewusst unangetastet gelassen — die Datei deklariert sich selbst bereits als alte, nicht vollständig umgesetzte Vorschlagssammlung, nicht als aktuelle Verhaltensdokumentation. Drei neue, maschinell prüfbare Regressionswächter inscripts/release-check.sh(unqualifizierte “WCAG 2.1 Accessibility Checker”-Phrase, HTML5-Conformance-“score-neutral”, HTML als Report-Format), je mit manuellem Negativfall verifiziert (alten Text testweise wiederhergestellt → Check schlägt fehl; zurückgesetzt → wieder grün). -
Release-Workflow: Qualitätsgates vor dem Artefakt-Build erzwungen, 2026-09-17:
.github/workflows/release.ymlsbuild-Job hing bisher nur vonverify-versionab — Tests, Formatierung, Clippy und das Dependency-Advisory-Gate waren keine Voraussetzung für einenv*-Tag-Release..github/workflows/ci.ymlbekommt einen zusätzlichenworkflow_call:-Trigger (nebenpush/pull_request),release.ymlruft es jetzt als neuen Jobquality-gatesperuses: ./.github/workflows/ci.ymlauf demselben Commit-SHA auf, denbuildjetzt zusätzlich zuverify-versionals Voraussetzung hat — technisch über den SHA abgesichert (derselbe Checkout), nicht nur über den Branch-Namen.browser-smoke/coveragelaufen dabei bewusst nicht erneut mit (ihr eigenesif: github.event_name == 'push' || ... == 'pull_request'schließtworkflow_callbereits aus) — die Chrome-/Corpus-Tests liefen schon beim Merge aufmainund laufen erneut lokal über den pre-push-Hook (scripts/release-check.sh), bevor überhaupt ein Release-Tag gepusht wird; sie im Release-Gate zu wiederholen hätte jeden Release unnötig verlangsamt. Beide Plattform-Build-Schritte bekommen--locked, damit Releases reproduzierbar aus dem eingechecktenCargo.lockgebaut werden. Zwei neuerelease_contract_tests(test_release_build_requires_quality_gates_to_pass_first,test_release_build_uses_locked_lockfile) verhindern, dass der Release-Job künftig wieder ohne Gate auf Artefaktbau zuläuft — Negativfall verifiziert (needs:auf nurverify-versionzurückgesetzt → Test schlägt fehl). -
Dependency-Sicherheit: RustSec-Gate ergänzt, 2026-09-17:
cargo auditmeldete 9 Schwachstellen imCargo.lock. Patch-Level-Updates ohne API-Auswirkung eingespielt:rustls0.23.40 → 0.23.45 (RUSTSEC-2026-0285),crossbeam-epoch0.9.18 → 0.9.21 (RUSTSEC-2026-0204),quinn-proto0.11.14 → 0.11.18 (RUSTSEC-2026-0185).lopdf(Dev-Dependency, PDF-Testhelfer) von 0.34 auf 0.45 angehoben (RUSTSEC-2026-0187) — die verwendete API (Document::load_mem,.objects,.catalog(),.get_pages(),.extract_text()) blieb über den Versionssprung unverändert, alle PDF-Tests weiterhin grün. Zwei Advisories bleiben offen und sind in.cargo/audit.tomleinzeln mit Begründung dokumentiert statt pauschal ausgeblendet:rsa(RUSTSEC-2023-0071, nur über die optionaleai-transparency/c2pa-Kette, kein Upstream-Patch verfügbar, auditmysite hält keinen privaten RSA-Schlüssel) undquick-xml(RUSTSEC-2026-0195/-0194, zwei transitive Kopien überhayagriva/syntect, jeweils bereits am von den Elternpaketen erlaubten Versions-Deckel — Fix erfordert ein Upstream-Release dertypst/renderreport-Kette; betroffene Eingaben sind gebündelte, nicht angreifer-kontrollierte Daten wie Zitationsstile und Theme-.plist-Dateien, nicht Inhalte geprüfter Webseiten). Neuer CI-Jobdependency-audit(.github/workflows/ci.yml) führtcargo auditauf jedem PR aus. -
Screenreader-Modul: Lesereihenfolge zählte Layout-Artefakte, 2026-09-17: Ausgelöst durch die Prüfung eines RankingLab-Reports (Sachsen-Anhalt): Der Abschnitt „Screenreader-Lesereihenfolge” meldete 237 „angekündigte Knoten” und vier Befunde „Langer Abschnitt ohne Landmark, Überschrift oder Fokusziel” — bei gleichzeitig 100/100 Heading- und Landmark-Qualität. Lokal an www.sachsen-anhalt.de exakt reproduziert. Ursache:
linearizeemittierte jeden nicht-ignorierten AX-Knoten alsReadingItem, darunter 67InlineTextBox— Blinks Zeilenfragmente, in die einStaticTextdurch Zeilenumbruch zerlegt wird. Sie existieren für Caret-/Auswahl-Bounds, werden nie an eine assistive Technologie gereicht, und trugen denselben Text zwei- bis viermal. Dadurch war jede aus der Lesereihenfolge abgeleitete Zahl aufgebläht: Die Desert-Heuristik zählte eine News-Teaser-Karte aus vier Ankündigungen (Schlagzeile, Datum, Teaser, Bildnachweis) als 23-Einträge-Wüste; alle vier Befunde der Seite waren False Positives, erzeugt allein vom Zeilenumbruch. Neu:linearizer::is_layout_only_roleüberspringtInlineTextBoxbeim Emittieren, traversiert aber weiter (Nachfahren bleiben erhalten);analyzer::carries_announced_contentzählt für die Desert-Distanz nur noch Items mit eigenem Namen oder Wert, sodass leere Wrapper (figure,paragraph,group,generic,Figcaption,list) nicht mehr dieselbe Ankündigung doppelt zählen — rollen-agnostisch, einaria-labeltergroupzählt weiterhin.CONSENT_WALL_NODE_THRESHOLDvon 200 auf 145 nachkalibriert, weil die Knotenzahl um denselben Anteil sinkt. Ergebnis an sachsen-anhalt.de: 237 → 170 Knoten, vier False Positives verschwunden, der eine echte Befund (Button ohne zugänglichen Namen, 4.1.2 → § 12 Nr. 3 BFSGV) bleibt; Tab-Stopps und alle drei Qualitätswerte unverändert. Verifiziert per Vorher/Nachher-Lauf und 4 neuen Unit-Tests. -
HTML-Konformität: Score zählte Fundstellen statt Ursachen, 2026-09-16: Das Modul stand auf
weight_pct: 0mit dem Report-Hinweis, sein HTML5-Schema kenne keine RDFa-/Open-Graph-Attribute undog:-Meta-Tags erzeugten deshalb False Positives. Beides war überholt: Die RDFa-Lücke ist seithtml-conform0.2.1 geschlossen (schema/html5/meta.rnc, Block „RDFa Lite Property Metadata”); an einer Seite mitog:-Tags direkt gegengeprüft — kein einziger solcher Befund. Der eigentliche Defekt war die Strafformelerror_count * 10: Sie rechnete pro Fundstelle, nicht pro Ursache. Ein fehlerhaftes Template-Element erzeugt eine Fundstelle pro Rendering, also floorte jede reale Seite bei 0 — bei sachsen-anhalt.de gingen 201 der 208 gemeldeten Fehler auf ein einziges<link as=…>ohnerelzurück, insgesamt 6 distinkte Ursachen. Neu:html_conform::defect_keynormalisiert Regel-ID plus Meldung (Backtick-Werte undat Zeile:Spalteentfernt) zu einer Ursache;score_findingsbestraft pro Ursache, mit gedecktem Häufungszuschlag (max. das Doppelte der Basis) und Rangdämpfung (erste Ursache voll, zweite/dritte halb, Rest ein Viertel), damit der Score über die ganze Spanne aussagekräftig bleibt statt bei 0 zu sättigen. Sachsen-anhalt.de damit 0 → 64. Das Modul zählt wieder in die Gesamtwertung (5 %, aus SEO umgeschichtet: SEO 20 → 15) und meldetmeasurement_type: "measured"statt"heuristic". Report-seitig: neues Felddistinct_defect_countim JSON, Kennzahl „Ursachen” im PDF, Findings-Tabelle eine Zeile pro Ursache mit Spalte „Vorkommen” (statt 20 redundanter Fundstellen-Zeilen),ScoreCardwieder mit normalem Notenband, der veraltete RDFa-Hinweis durch eine Erklärung der Ursachen-Zählung ersetzt. Verifiziert an sachsen-anhalt.de (JSON +--debug-typ) und mit 8 neuen Unit-Tests. -
PDF-Abbruch durch fremde Glyphen und Panic im HTML-Content-Model, 2026-09-16: (1) Ein einzelnes Icon-Font-Zeichen auf der auditierten Seite brach die gesamte PDF-Erzeugung ab: Linktexte, Selektoren und Custom-Property-Namen tragen Private-Use-Codepoints in den Report, Typst fällt auf
LastResortzurück und der PDF/UA-Export scheitert (PDF/UA-1 error: the text "\u{e900}" could not be displayed). Betraf 8 von 84 Domains eines RankingLab-Laufs (u. a. kit.edu, fu-berlin.de, uni-goettingen.de). Neu:output/pdf/sanitize.rsentfernt Private-Use-Zeichen aus allen Strings desRenderRequest(ein Chokepoint nachbuilder.build(), deshalb ohne Feld-für-Feld-Pflege) und lernt die übrigen nicht darstellbaren Zeichen aus dem Compile-Fehler: Zeichen verwerfen, neu rendern, max. 8 Runden — so fiel auf jyu.fi ein↳aus einem CSS-Variablennamen. Nur Präsentationsschicht; das kanonische JSON behält den Originaltext, damit Selektoren kopierbar bleiben. (2)enclosing_tag_stack(wcag/rules/html_content_model.rs) panickte auf ikea.com, weil der Byte-Offset des externen Parsers mitten in einem\u{a0}lag — entgegen der eigenen Zusage „never panics”. Offset wird jetzt auf die Zeichengrenze zurückgesetzt, der Raw-Text-Close-Tag-Vergleich läuft über Bytes stattstr-Slices. Verifiziert an den Live-Domains plus je zwei Regressionstests. -
1.3.1-Fixes, 2026-09-16 (#581, #582, #583): (1) Die Accessibility-Subkategorie- und Security-Kategorie-Scores aus
d6daf7berschienen nur im PDF, nicht im JSON-Report — der JSON-Builder (build_page/PageDetail) las sie nie. Jetzt alspages[].detail.accessibility_subcategory_scores/security_category_scores({ name, score }, kanonisch Englisch) im Single-Report, mit Schema-, Registry- undOUTPUT_CONTRACT.md-Eintrag; Batch-Details bleiben kompakt und lassen beide weg. (2) Der CI-Schritt für die vier Detection-Corpus-Binaries brach beim ersten Fehlschlag ab, die übrigen liefen nie (so im ersten 1.3.0-CI-Lauf passiert) — jetzt ein Schritt pro Binary mitif: !cancelled(),release-check.shsammelt Fehlschläge und bricht erst am Ende ab. (3)Cargo.lockhielt das gelöschtespin 0.9.8(überlazy_static) — auf 0.9.9 gehoben. -
Leerseite nach leerem Report-Teil behoben, renderreport 0.5.1, 2026-09-15: der
pdf-smoke-Testtest_single_pdf_technical_pages_are_not_blank_when_pdftoppm_is_availableschlug fehl: im Technical-Fixture folgte auf die Teil-3-Trennseite (seit plan/22 mit eigenem abschließendemPageBreak) kein Modulinhalt, sondern direkt der Anhang mit eigenemPageBreak— dazwischen eine leere Seite. Ursache lag in renderreport: die Engine schrieb fürLayoutHint::AlwaysNewPage(nurpage-breaknutzt ihn) einen harten#pagebreak()vor jede eigentlich weiche Umbruch-Komponente, zwei benachbarte Umbrüche ergaben so eine Leerseite. renderreport 0.5.1 schreibt dort einen weichen Umbruch (mit Regressionstest, der ohne Fix rot war). Echte Reports waren in drei Live-Läufen (Standard, Technical, ohne--full) nicht betroffen, weil Teil 3 dort immer Inhalt hat. -
Batch-Fortschritt vor dem Report abschließen, 2026-09-15 (#580):
run_batch_mode(src/cli/runners.rs) rieffinish_verdicterst nachoutput_batch_reportbzw.output_batch_as_single_reportsauf — im TTY zeichnete der aktive Balken über den Report, in einer Pipe kam die Verdict-Zeile nach dem Report (Regression gegen das #530-Abnahmekriterium). Die Reihenfolge ist jetzt inBatchLifecyclePresenter::finish_then_renderfestgelegt, beide Zweige nutzen sie. Regressionstestlifecycle_is_finished_before_report_renders; Live per Pipe mit der Reproduktion aus dem Issue verifiziert (Verdict-Zeile vor dem Report, ANSI-frei). -
CLAUDE.mds „Current State”-Historie nachCHANGELOG.mdausgelagert, 2026-09-06 (plan/11-claude-md-version-drift.md): die Datei war auf 1058 Zeilen gewachsen, davon 805 reine chronologische Historie.CLAUDE.mdbehält jetzt nur Arbeitsregeln + einen kurzen Verweis hierher; neue Einträge kommen künftig hier rein, nicht mehr inCLAUDE.md. Reiner Cut-Paste, kein Eintrag inhaltlich verändert. -
Plan-Review-Batch komplett abgeschlossen, 2026-09-06 (Punkte 7–11): Punkt 7 (Management- Summary verdichten) auf Nutzerwunsch als „leichtere Verdichtung” statt exakt 2 Seiten umgesetzt:
render_module_split_dashboardss Stärken/Schwächen-Karten zeigten dieselben 4-5 gewichteten Module (Accessibility/Performance/Security/Mobile) bereits ein drittes Mal (nach Radar-Chart und der neuen Score-Driver-Tabelle aus Punkt 2) — jetzt aufmeasurement_type != "measured"gefiltert, zeigt also nur noch die nicht-gewichteten Indikator-Module (UX, Journey, Search Experience, …), inklusive angepasster „alles gut”-Fallback-Nachricht (scoped auf „zusätzliche Indikator-Module” statt pauschal „alle Module”). Neuer Hebelwirkungs-Satz auf der Maßnahmenplan-Seite („Die N wichtigsten Ursachen erklären rund X % der erkannten Vorkommen — ihre Behebung hat die größte Hebelwirkung …“) — teilt sich die Berechnung mit dem Diagnose-Satz aus Punkt 3 über eine neue gemeinsamecompute_problem_concentration-Funktion (eine Zahl, zwei unterschiedlich formulierte Sätze auf zwei Seiten, kein Duplikat-Text). Beide Änderungen per--debug-typ-Smoke-Test verifiziert (Karten verschwinden korrekt, wenn keine Indikator-Module vorhanden sind; Hebelwirkungs-Satz rechnet korrekt „4 Ursachen, 50 %, 8 Vorkommen” auf der Fixture). Punkte 8–11 (Detection-Corpus-Tests im Release-Skript, README- Genauigkeitsanspruch, Datei-Größen-Bestätigung, Versionsnummer + Modul-Liste) waren kleine, unabhängig verifizierte Fixes — sieheplan/status.mdfür die Details je Punkt. -
Report-Review-Batch plan/1–6 umgesetzt, 2026-09-06 (externes Report-Review zu inros-lackner-de): sieben destillierte Punkte, sechs umgesetzt (Punkt 7 — Management-Summary 4→2 Seiten — bewusst zurückgestellt, Layout-Entscheidung braucht separate Nutzer-Bestätigung). Punkt 1 (P0) legte einen deutlich größeren Bug frei als gemeldet: der gemeldete Fall (ein
landmark-unique-Finding zeigte den falschen, generischen WCAG-1.3.1-Titel “Fehlende semantische Struktur” statt “Landmarks nicht eindeutig benannt”) war ein einfacher fehlenderexplanations.rs-Eintrag (#571-Muster). Die Root-Cause-Untersuchung deckte aber einen zweiten, strukturellen Bug inwcag_group_key(src/audit/normalized.rs) auf: jederaxe_idohne eigenenLEGACY_WCAG_MAP-Eintrag fällt auf die rohe WCAG-Kriteriums-ID als Gruppierungs- schlüssel zurück — teilen sich zwei unabhängige Checks ein Kriterium (z. B. mehrere 1.3.1-Landmark-Checks, mehrere 4.1.2-ARIA-Checks), vermischen sich ihre Violations zu einem einzigen Finding mit summierter Occurrence-Zahl und dem Titel des zufällig ersten Treffers. Ein systematischer Scan vonsrc/wcag/rules/*.rsfand 43 betroffene axe_ids über 10 WCAG- Kriterien (1.1.1, 1.3.1, 1.4.4, 2.1.1, 2.4.1, 2.4.4, 2.4.7, 3.3.1, 4.1.1, 4.1.2) — nicht nur die 14 axe_ids unter 1.3.1, die der gemeldete Fall vermuten ließ. Jeder betroffene axe_id bekam einen eigenentaxonomy::rules::Rule-Eintrag +LEGACY_WCAG_MAP-Eintrag; 32 davon zusätzlich eine neue zweisprachigeRuleExplanation, 3 (region,focusable-no-role,link-name-context) hatten bereits eine passende Explanation, brauchten nur den fehlenden Rule/Map-Eintrag. Zwei axe_id-String-Kollisionen zwischen unabhängigen Checks entdeckt und aufgelöst (Umbenennung, da ein globalerLEGACY_WCAG_MAP-Eintrag sonst beide Checks fälschlich zusammengeführt hätte):labels.rss 4.1.2-Check"label"→"control-missing-label"(Kollision mitinstructions.rs/form_rules.rss absichtlich geteiltem 3.3.2-"label"),link_purpose_link_only.rss 2.4.9-Check"link-name"→"link-name-only"(Kollision mitlink_purpose.rss 2.4.4-"link-name"). Ein bereits bestehender Test (tests/wcag_coverage.rs::no_undocumented_severity_collisions_in_group_key_mechanism, Teil der QA-009-Nachverfolgung) bestätigte den Fix unabhängig: er verlangte, 5 der 6 dokumentiertenALLOWED_MIXED_SEVERITY_GROUPS-Ausnahmen zu entfernen, weil ihr Severity-Mix nach dem Fix nicht mehr auftritt — die verbleibende3.3.2-Ausnahme ist ein bewusst geteilter axe_id (kein Grouping-Bug). Punkt 2 (Score-Driver-Tabelle): neueModulesBlock.module_scores(roh ausNormalizedReport.module_scores, nicht aus dem für die Radar-Karten umgeformtendashboard, da z. B. die “Sichtbarkeit & Nutzerverständnis”-Karte SEO mit heuristischen KI-Sichtbarkeits-Signalen zu einem Komposit-Score verschmilzt, der nicht der rohe SEO-Score mit SEO’s echtem Gewicht ist) speist eine neue Tabelle direkt nach dem Qualitätsprofil-Radar: Modul/Score/Gewichtung/Einordnung (“Größter Schwachpunkt”/“Erheblicher Risikotreiber”/ “Stabilisiert Gesamtwert”), Einordnung nach(100-score)*weight_pct-Drag statt rohem Score (ein per Live-Smoke-Test gefundener und gefixter Bug: die erste Fassung sortierte nach Score mit Gewichtung nur als Tie-Breaker und wählte fälschlich das niedrig gewichtete Security 30 statt des hoch gewichteten Accessibility 40 als “größten Schwachpunkt” — genau der Fall, den der Review als Beispiel nannte). Punkt 3: neuer generierter Diagnose-Satz (“N Vorkommen erkannt, rund X % auf 4 wiederkehrende Ursachen zurückführbar, besonders betroffen: …”) direkt nach der Score-Driver-Tabelle, Scope bewusst Accessibility+SEO kombiniert (Nutzerentscheidung). Punkt 4: “Root Cause”-Formulierungen vereinheitlicht auf vorsichtigeren Wortlaut (“X Vorkommen folgen demselben Muster — deutet stark auf … hin, bestätigt erst mit Belegen von weiteren Seiten”), dabei einen unabhängigen #406-Lokalisierungs-Leak gefunden und gefixt (src/output/pdf/findings.rs: das “Root Cause”-Karten-Label war fest Englisch, auch in deutschen Reports) sowie ein identisches hartcodiertes Duplikat inbuilder/batch.rsmitgezogen. Batch-Reports’TemplateCluster::confidence(confirmed/likely) erfüllte die im Plan gestellte Frage nach stärkerer, tatsächlich begründeter Formulierung bei Mehrseiten-Evidenz bereits — keine Änderung nötig. Punkt 5: neues Scoring-Subsystem, da entgegen der Plan-Annahme noch gar kein echter 0–100-Subkategorie-Score existierte (nur eine Taxonomie-Kategorisierung für Labels und ein heuristischer Top-3-Text). Accessibility:compute_accessibility_subcategory_scorespartitioniert dieselben rohenViolations nachtaxonomy::Subcategory(via der schon vorhandenenwcag_group_key-Auflösung) und lässt den echtenAccessibilityScorer::calculate_scorepro Subcategory-Teilmenge laufen — kein neu erfundenes Penalty-Schema. Security hat keine WCAG-äquivalente Rule-Registry; neuessecurity::SecurityCategory(Response-Header/Transportsicherheit/Zugriffs-Richtlinien/ Drittanbieter-Exposition) gruppiertSecurityIssue.header-Strings und nutzt dieselbe Severity→Penalty-Zuordnung wiecalculate_security_score. PDF zeigt „Kritische Schwächen: … · Vergleichsweise stabil: …” (Schwelle: bestehenderFIVE_BAND-Cutoff bei 60, keine neue Schwelle erfunden), pro Modul nur wenn beide Seiten nicht leer sind. Punkt 6: neueRoadmapItemData.leverage(“Sehr hoch”/“Hoch”/“Niedrig”, feste Regel ausoccurrence_count≥10 → Sehr hoch, sonst ≥5 Vorkommen oder Quick-Fix+hohe Priorität → Hoch, sonst Niedrig — die 10er-Schwelle spiegelt bewusst die bestehendeFindingGroup::is_component_issue-Schwelle) als neue Zeile im Maßnahmenplan-Karten-Text, Wortwahl “betroffen” statt “behoben” (konsistent mit Punkt 4). Alle sechs Punkte percargo test/--no-default-features/--all-features+cargo clippy --all-targets --all-features -D warnings+ mehreren--debug-typ-Smoke-Tests gegen angereicherte Fixtures verifiziert (1368 Tests im vollenpdf_test-Feature-Lauf, 0 Fehlschläge). -
CI seit mehreren Wochen durchgehend rot — echte Ursache gefunden und gefixt, 2026-09-06 (Repo-Aufräum-Session): beim Aufräumen
gh run listgeprüft (vorher nie gemacht — lokal wurde immer nurcargo check --all-featuresper Pre-Push-Hook verifiziert, nie der tatsächliche CI-Status) und festgestellt, dass praktisch jeder Push seit mindestens 2026-07-16 in CI fehlschlug. Ursache:src/seo/schema.rss Testastro_structured_data_exports_work_...lädt seine Fixture perinclude_str!("../../tests/fixtures/astro_structured_data_components.json")— diese Datei lag lokal auf der Platte, war aber nie in Git getrackt (von.gitignores pauschalem*.jsonerfasst, keine Whitelist-Ausnahme wie bei den anderen Fixture-Verzeichnissen).cargo check(der Pre-Push-Hook) kompiliert keine Test-Targets und hat das nie bemerkt; jedercargo test/cargo clippy --all-targets-Lauf in CI brach dagegen mit einem harteninclude_str!-Compile-Fehler ab, bevor auch nur ein Test lief — betraf praktisch alle CI-Jobs (Check & Test, PDF Smoke Tests, Coverage). Gleiche Fehlerklasse wie derdetection_corpus_nonwcag/*/*.html-Gitignore-Gap aus plan/11 (Symptom: lokal vorhandene, aber nie committete Fixture-Datei), hier aber mit echtem CI-Impact statt nur “würde beim nächsten Hinzufügen verlorengehen”..gitignoreum!tests/fixtures/astro_structured_data_components.jsonergänzt, Datei eingecheckt. Dabei zusätzlichtests/wcag_fixtures/*.html(7 Dateien, vontests/coverage_matrix.rsper Laufzeit-Verzeichnis-Scan genutzt, keininclude_str!— daher kein CI-Blocker, aber derselbe Tracking-Gap) mitgefixt. Lokal mit den exakten CI-Befehlen verifiziert:cargo clippy --all-targets --all-features -- -D warnings,cargo clippy --no-default-features -- -D warnings,cargo test --no-default-features— alle grün. -
Repo-Konsolidierung, 2026-09-06: verwaisten Worktree + Branch
worktree-agent-*entfernt (0 eigene Commits, die nicht schon in main waren — reiner Leftover eines altenisolation: "worktree"-Agent-Laufs).archive/main-history(lokal + origin) auf expliziten Nutzerwunsch gelöscht, nur nochmainexistiert. Streudateien im Projekt-Root entfernt (delete.html, ein liegengebliebener Shopify-Seiten-Dump; ein eigener Test-Rest-JSON aus einer vorherigen Session).reports/(68 MB) undtmp/pdfs/(lokale, gitignorte Testreport-Ausgaben) auf Nutzerwunsch komplett geleert — regenerierbar, kein Git-Tracking betroffen. Lokalertarget/-Ordner-Rest bereinigt (CARGO_TARGET_DIRzeigt eigentlich nach~/.cargo/target, siehereference_cargo_target_dir.md). -
plan/11: Non-WCAG-Detection-Corpus für vulnerable_libs/schema_rules befüllt, 2026-09-06 (Rest-Scope von #558 abgeschlossen — Security-Domain war bereits fertig): beide Domänen brauchen echtes Chrome/CDP (anders als Security, das nur HTTP-Fetch ist), daher zwei neue
#[ignore]-gegatete Tests analogtests/detection_corpus_test.rs. vulnerable_libs (8/8 covered):tests/vulnerable_libs_detection_corpus_test.rsdiffedanalyze_vulnerable_libraries’s echten Output gegen 4 kombinierte HTML-Fixtures (nicht mehr, da minimale globale Stubs reichen —window.jQuery.fn.jqueryetc., keine echte Bibliotheksfunktionalität nötig). Auf 4 statt 2 Dateien aufgeteilt wegen eines echten, vom Modul selbst dokumentierten Constraints: Lodash und Underscore teilen sich das globale_und würden sich bei gemeinsamer Nutzung gegenseitig überschreiben. Prototype/MooTools sind im Code unconditional als vulnerable markiert (unmaintained), haben also nur einen Violation-, keinen Pass-Fixture. Dabei einen echten, vorbestehenden Bug gefunden und gefixt:.gitignores*.html-Sperre hatte eine Ausnahme fürtests/fixtures/detection_corpus/*.html(WCAG-Corpus), aber keine für das gleichrangigedetection_corpus_nonwcag/*/*.html— neue Fixtures in dieser Domäne wären lokal vorhanden, aber nie eingecheckt worden, unbemerkt. schema_rules (17/17 covered): vor dem Bau des vollen Corpus geprüft und festgestellt, dassschema_rules.rss reineassess_node-Regellogik bereits für 14 von 17 Features sehr ausführliche Pure-Rust-Unit-Tests hat (Grenzfälle wie EventRescheduled, Remote-Jobs, Merchant-vs-Editorial-Context) — dem Nutzer vorgelegt, der sich trotzdem für den vollen Chrome-Corpus entschieden (Tracking-Konsistenz mit der Security-/vulnerable_libs-Domäne wichtiger als Redundanzvermeidung).tests/schema_rules_detection_corpus_test.rsdiffeddetect_structured_datas echten Output gegen 2 kombinierte Fixtures (alle 17 Typen vollständig/gültig → erwartet “pass”; die 11 Typen, die überhaupt Pflichtfelder haben können, unvollständig → erwartet “violation”).merchant_listingerwartet bewusstneeds_review, nicht pass/violation:detect_structured_datawertet Regeln immer mitProductRuleContext::Indeterminate, der echte Seiten-Intent-Kontext wird erst später vonseo::modulesderive-Schritt überschema_fit/refresh_rule_assessmentsgesetzt — das ist reales, korrektes Verhalten dieses Einstiegspunkts, keine Testlücke. Fünf Features (Article/Organization/WebPage/WebSite/Person) haben inassess_nodekeine Pflichtfelder und können strukturell nie “violation” werden — im “complete”-Fixture als “pass” mitgetestet, kein eigener Violation-Fixture nötig. Beide neuen Tests per absichtlich verfälschtemexpected.jsonverifiziert, dass sie einen echten Diff erkennen (danach zurückgesetzt). -
plan/18: zwei neue Best-Practice-ARIA-Hygiene-Regeln, 2026-09-06 (letzter Punkt aus dem aktuellen Insights-Artikel-Batch):
redundant_role.rs(4.1.2/redundant-role, z. B.<button role="button">,<a href="..." role="link">— fixe, kontextunabhängige Tag-zu-implizite-Rolle-Tabelle; Tags mit vorfahrenabhängiger Rolle wie<header>/<footer>bewusst ausgeschlossen) undfake_navigation_link.rs(4.1.2/link-as-button,<a href="#" onclick="...">/href="javascript:..."als Button-Ersatz — nur inlineonclick, dieselbe Grenze wieclick_handlers.rs). BeideSeverity::Low,WcagLevel::A, Tag"best-practice", registriert im normalenPageRuleEntry-Mechanismus (page_rules.rs), keine neue Infrastruktur. Zwei reale Bugs beim Live-Verifizieren gefunden und behoben, nicht nur Wording: (1) erste Fassung baute Selektoren manuell aus Tag+id+class (wieclick_handlers.rses vormacht) — bei mehreren gleichnamigen Tags ohne id/class (z. B. zwei<a>-Elementen ohne Klasse) kollidierten beide auf denselben Selector-String"a", nicht in der Fixture reproduziert zunächst unbemerkt, aber real:js_helpers::CSS_SELECTOR_JS’s eigener Doc-Kommentar warnt explizit davor (“Never falls back to a bare tag name”). Auf den robusteren, bereits vorhandenen__amsCssSelector-Helper umgestellt (wiearia_prohibited_attr.rs), live verifiziert:body > a:nth-of-type(1)/:nth-of-type(3)/:nth-of-type(4)bleiben jetzt unterscheidbar. (2) Naive String-Konkatenation vonCSS_SELECTOR_JS(eine Top-Levelfunction-Deklaration) direkt gefolgt vom eigenen, selbständigen(function(){...})()erzeugte einen echten Live-Fehler (TypeError: (intermediate value)(intermediate value)... is not a function) —Runtime.evaluateparst ein führendesfunction-Token an Ausdrucksposition als Funktionsausdruck statt als Deklaration, wodurch die folgende IIFE als Funktionsaufruf-Argument interpretiert wird statt als eigenständiges Statement. Behoben nach demselben Muster wiearia_prohibited_attr.rs: beides in ein explizites äußeres(function() { ... })()einbetten, statt zwei unabhängige Top-Level-Snippets zu verketten — beim ersten Versuch übersehen, weilclick_handlers.rs(das ursprüngliche Vorbild)CSS_SELECTOR_JSgar nicht nutzt und dieses Konkatenationsproblem daher dort nie auftritt. Live gegen eine lokalefile://-Fixture verifiziert (5 Testfälle: 3× redundante Rolle, 2× Fake-Navigation, 2× korrekt nicht geflaggte legitime Fälle) — beide Regeln feuern exakt wie erwartet, keine False Positives. Zwei hartcodierte Page-Rule-Zähler-Regressionstests (level_a_filter_includes_only_level_a_rules,level_aa_filter_includes_aa_plus_a_rules,page_rules.rs) entsprechend hochgezählt (36→38, 47→49). Kein neuer Detection-Corpus-Fixture-Eintrag (#556-Format) — passend zum bestehenden Präzedenzfall reiner DOM-JS-Regeln ohne Offline-Unit-Test in diesem Modul. -
plan/17: 4.1.3-Vertiefung — Live-Region-Spät-Einfügung erkannt, 2026-09-06: neuer
InteractiveFindingKind::FormErrorLiveRegionLateInsertion(Severity::Low, Advisory) insrc/a11y_journey/form_error.rs— Erweiterung der bestehenden Form-Error-Journey statt neuer Erkennungspfad, wie im Plan gefordert:check_error_stateerfasste bereitslive_before(vor Submit) undlive_after(nach Submit),new_live = live_after && !live_beforewurde bisher nur intern zur Ableitung vonFormErrorInvalidWithoutLiveRegiongenutzt, nie selbst als eigener Befund gemeldet. Neuer Check: wennnew_livewahr ist (Live-Region hat korrekt angekündigt, war aber initial nicht im DOM), wird das jetzt als eigenständiger Advisory-Befund gemeldet — klar getrennt vom bestehenden “gar keine Ankündigung”-Fall (FormErrorInvalidWithoutLiveRegion/Silent-Failure, die beide!new_livevoraussetzen, also nie gleichzeitig mit dem neuen Befund feuern) und vom Politur-Konflikt-Check instatus_messages.rs(unverändert). Bewusst Advisory/Low statt High: die Ankündigung hat in diesem Lauf funktioniert, es ist eine Robustheits-Empfehlung für andere Browser-/AT-Kombinationen, kein bestätigter Fehler. Live gegen casoon.de (Startseite + /kontakt) geprüft — dort wurde kein Form-Error-Journey-Kandidat erkannt (keine passende Formular-/Submit-Struktur auf diesen zwei Seiten), der neue Codepfad ist daher nur durch Build/Clippy/die volle Testsuite (1318 Tests) und den EN-Locale-Umlaut-Guard verifiziert, nicht durch einen live beobachteten Treffer — wie alle anderenFormError*-Befunde in dieser Datei hatform_error.rskeine eigene Unit-Test-Infra (Chrome-Page-Interaktion, kein Mocking-Präzedenzfall in diesem Modul). -
plan/16: konkrete AT-Selbsttest-Anleitungen statt generischem “manuell prüfen”, 2026-09-06: neue
manual_recheck_instruction(wcag_criterion, en)(src/output/pdf/helpers.rs), keyed nach WCAG-Kriterium, mit VoiceOver-Rotor-/NVDA-Elementliste-Tastenkürzeln für die vier Kriterien, bei denen ein Laie einen Verdacht in wenigen Minuten selbst nachprüfen kann: 1.3.6/1.3.1 (Landmarken/Überschriften via Rotor VO+U bzw. Insert+F7), 3.3.2 (Formularfeld-Labels via VO+Cmd+J/NVDA F), 4.1.3 (Fehlermeldungs-Ansage — Meldung auslösen, prüfen ob automatisch ohne Fokus-Sprung angesagt). Bewusst keine neue JSON-Struktur/kein kind-Enum — reiner PDF-Präsentationstext (analogrender_manual_only_criteria_note, plan/15), an drei bereits bestehenden Stellen angehängt statt eines neuen, vom Finding losgelösten Anhangs: der Screenreader-Befunde-Tabelle (detail_modules/accessibility.rs, keyed aufissue.wcag_criterion), der WCAG-Violation-Finding-Karte (findings.rs, neue “Selbsttest”-Zeile im Maßnahmenbewertungs-Block, keyed aufgroup.wcag_criterion) und der Manuelle-Prüfpunkte-Checklist (single_report.rs, an denrecommendation/text-String angehängt, keyed auffinding.rule).1.3.1deckt bewusst sowohl Heading- als auch Landmark-Duplikat-Funde mit einem gemeinsamen Text ab (beide Sub-Fälle teilen dieselbewcag_criterion-Konstante im Code und werden im selben VoiceOver-Rotor/NVDA-Elementliste-Tool geprüft, keine künstliche Trennung nötig). Live gegen casoon.de verifiziert (--debug-typ): die Landmarken-Anleitung erscheint korrekt an jedem 1.3.6-Screenreader-Befund. -
plan/15: feste “Strukturell nur manuell prüfbare Kriterien”-Liste im PDF, 2026-09-06: neue
render_manual_only_criteria_note(src/output/pdf/single_report.rs), direkt nach der WCAG-Coverage-Sektion, unconditional (kein--annex-Flag, gleiches Report-Level-Gate wie die Coverage-Sektion selbst). Fünf feste, seiten-unabhängige Zeilen (2.4.3 Fokusreihenfolge, 3.3.1 Fehlerkennzeichnung, 1.4.4/1.4.10 Zoom & Reflow, 1.1.1 Alt-Text-Korrektheit, 1.2.1/1.2.2 Video-Inhaltszusammenfassung) — bewusst NICHT aus den Findings dieser Seite abgeleitet wie EN-301-549-/BIK-Annex, weil genau die dritte, “unsichtbare” Konfidenzkategorie aus dem Insights-Artikel (plan/13) sonst bei einem 0-Findings-Report komplett unsichtbar bliebe. Live gegen casoon.de verifiziert (--debug-typ): rendert korrekt, ohne Status-Punkt (kein good/warn/bad-Urteil, reine Scope-Aussage). Beim Umsetzen entdeckt: es gibt bereits eine separate, ähnlich benannte “So testen Sie manuell”-Sektion (src/output/pdf/wcag_coverage.rs) mit AT-Testanleitungen (Tastaturnavigation, Screenreader, 400%-Zoom, Reduced Motion, …) — andere Funktion (Anleitung “wie testen” statt Erklärung “warum strukturell nicht automatisierbar”), bewusst nicht zusammengelegt, aber relevanter Ankerpunkt für plan/16. -
plan/14: “Prozess statt Zertifikat”-Satz im Disclaimer ergänzt, 2026-09-06: Prüfung ergab, dass der Scope-Vorbehalt selbst (“kein vollständiger Konformitätsnachweis”, “ersetzt keine manuelle Prüfung”) bereits im bestehenden
disclaimer-Text (build_methodology,src/output/builder/single/methodology.rs) und im Cover-Label (“WCAG-Vorkommen” statt pauschal “Accessibility-Befunde”) vorhanden war — beide Fundstellen unverändert gelassen. Fehlend war die im Artikel betonte Prozess-Aussage: Barrierefreiheit als fortlaufende Aufgabe, nicht einmalig erreichbarer Zustand. Ein Satz an den bestehendendisclaimerangehängt (DE+EN, keine neue Callout-Komponente, keine Änderung angate_certificate_by_risk/CERTIFICATE- Schwellen). Bewusst nicht auf dem Cover platziert — das ist eine feste, knappe Dashboard-Seite (renderreportCoverPage), Scope-/Rechtstexte leben in diesem Projekt durchgehend im Methodology-Kapitel, nicht auf dem Cover. -
plan/13 (Drei-Stufen-Konfidenz-Kennzeichnung) als bereits erfüllt geschlossen, 2026-09-06: der aus einem Insights-Artikel abgeleitete Plan-Punkt wollte ein neues
DetectionConfidence- Enum (Certain/HeuristicSuspicion/ManualOnly) plus Registry-Klassifikation prorule_idund ein neues PDF-Badge einführen. Bei der Umsetzung geprüft und verworfen:NormalizedFindingträgt bereitsconfidence/false_positive_risk/verification(heuristisch pro Regel viaderive_confidence/derive_false_positive_risk/derive_verification,src/audit/normalized.rs), und jede Finding-Karte im PDF zeigt unconditional “Erkennungssicherheit”/“Falschpositiv-Risiko” im “Prüfmetadaten”-Block (src/output/pdf/findings.rs) — nicht nur für Verdachtsfälle. Ein erster Versuch, das bestehende (ausfalse_positive_risk1:1 abgeleitete)verification-Feld zusätzlich symmetrisch als weitere Zeile zu zeigen, wurde wieder verworfen: das hätte dieselbe Information ein drittes Mal in anderen Worten auf derselben Karte gezeigt — echte Redundanz statt Erkenntniswert, das Gegenteil dessen, wasreport-lint/report-criticin diesem Projekt verhindern sollen. Die “nur kontextuell entscheidbare” dritte Stufe (Media-Alternative, Kontrast bei Bild-Hintergrund) läuft bereits sichtbar getrennt unter der PDF-Sektion “Manuelle Prüfpunkte und heuristische Hinweise” (render_assessment_and_execution_notes) mit eigenem “Manuelle Prüfung”-Label. Keine Code-Änderung — der Plan-Punkt war beim genauen Abgleich bereits durch bestehende, anders benannte Infrastruktur erfüllt. Der Plan-Text enthielt zudem einen inneren Widerspruch (Punkt 1 vs. Punkt 4 zur Einordnung von Media-Alternative alsManualOnlyvs.HeuristicSuspicion), der beim Nutzer geklärt wurde (Punkt 1/ManualOnlyist korrekt). -
build_idbekommt-dirty-Suffix bei uncommittetem Arbeitsstand (plan/21, 2026-09-05):build.rslas denbuild_idbisher nur ausgit rev-parse --short HEAD— solange (wie in diesem Projekt üblich) uncommittet gearbeitet wird, blieb der eingebettete SHA über mehrere tatsächlich unterschiedliche Builds hinweg identisch und machte zwei Live-Reports mit unterschiedlichem Fix-Stand nicht unterscheidbar (live an drei Reports vom 2026-09-05 bestätigt, alle mitbuild_id: "be9aed2"trotz seither committeter Änderungen). Neueis_dirty()-Prüfung (git status --porcelain, gleicher No-Git-Fallback wie beim SHA-Lookup — Fehler/kein Git ⇒ nicht dirty, kein Hard-Fail) hängt bei uncommittetem Stand-dirtyan, z. B.44e7480-dirty. Da der Dirty-Status sich bei jeder beliebigen Arbeitsbaum-Änderung ändern kann, nicht nur bei einem HEAD-/Ref-Wechsel, reicht das bestehendererun-if-changed=.git/HEADallein nicht — zusätzlicherrerun-if-changedauf einen garantiert nicht existierenden Pfad zwingt Cargo, das Build-Script bei jedem Build neu auszuführen (empirisch gegen ein Scratch-Projekt verifiziert: 3 Builds → 3 Skript-Läufe). Live gegen den eigenen, absichtlich uncommittet gehaltenen Arbeitsstand verifiziert (stringsauf der gebauten Binary zeigt44e7480-dirty). -
Verifiziert: 1.3.6-Fundzahl auf casoon.de kein Zählfehler (plan/22, 2026-09-05): ein Live-Report hatte 200 Vorkommen für WCAG 1.3.6 auf einer einzigen Seite gezeigt, als niedrig priorisierte Beobachtung ohne Verdacht auf konkreten Bug angelegt. Verifiziert gegen einen frischen Live-Lauf (
--format jsongegen www.casoon.de): kein Doppelzählungs-Bug über Desktop-/Mobile-Viewport hinweg (build_sr_audit_reportläuft nur einmal pro Report, auf einem einzigen AXTree) und keine echte Diskrepanz durch denis_real_node_id-Sanitizing-Filter (analyzer.rssanalyze_reading_sequenceentfernt leere/synthetische IDs ausaffected_node_idsnach der Analyse — Meldungstexte wie “18 entries” zählen bewusst die ungefilterte Rohmenge, währendaffected_node_ids.len()danach kleiner sein kann, z. B. 8).derive_bik_chapterss Structure-Kapitel summiertissue.affected_node_ids.len()über alle1.3.6-getaggten Funde einer Seite (identisches, bereits bestehendes Aggregationsmuster wie in jedem anderen Kapitel/Kriterium) — auf casoon.de sind das 14 separate, durchdetect_announcement_desertsgefundene “long section ohne Landmark/Heading/Fokusziel”-Stellen, deren gefilterteaffected_node_ids-Längen sich exakt zu 200 aufsummieren. Reale, verifizierte Struktureigenschaft der Seite (viele lange Content-Abschnitte ohne Orientierungspunkt dazwischen), kein Tool-Bug — keine Verhaltensänderung nötig. Klärender Kommentar inbik_guide.rsergänzt, damit künftige Reviewer die Summenbildung nicht erneut als Zählfehler missverstehen. -
#406-Fix: NotTestable/Warning-Findings zeigten rohen Englisch-Text im PDF (plan/20, 2026-09-05): live in DE-Reports bestätigter Lokalisierungs-Verstoß (casoon-de, satower-mosterei-de, inros-lackner-de) —
render_assessment_and_execution_notes(src/output/pdf/single_report.rs) rief fürwcag.not_testablesnurget_explanation(rule_id)auf (axe_id-Lookup, z. B."video-caption") und fiel bei fehlendem Treffer auf den rohen kanonisch-englischenfinding.fix_suggestionzurück statt auf den sicheren lokalisierten Fallback-Satz;wcag.warnings(Kontrast bei Bild-/Gradient- Hintergrund, 1.4.3) versuchte gar keinenget_explanation-Lookup. Fix: zweistufiger Lookup (rule_idzuerst, dannrule/WCAG-ID als Fallback — deckt sowohl axe-id-Override-Einträge wie"region"als auch die neuen WCAG-ID-Einträge ab) in beiden Schleifen, mit dem bestehenden lokalisierten Fallback-Satz als letztem Schritt stattfinding.fix_suggestion. Vier neueexplanations.rs-Einträge für die zuvor komplett fehlende 1.2.x-Video-Familie (1.2.1, 1.2.2, 1.2.3, 1.2.8). Realer, dabei entdeckter Nebeneffekt behoben:wcag::bik_guide::derive_bik_chapterss “Videos”-Kapitel zeigteNoFindingsDetected, obwohl ein echter, PDF-sichtbarer 1.2.2-Fund existierte —findings[](NormalizedFinding) wird ausschließlich auswcag_results.violationsgebaut (audit::normalized::normalize), nie auswarnings/not_testables, und Video-Checks resolven fast immer zu genau diesen beiden Kinds.derive_bik_chaptersnimmt jetzt zusätzlich&[AccessibilityAssessment](NormalizedReport.accessibility_assessments, bereits vorhandener Träger für genau diese Daten, in jedem Report-Pfad verfügbar) und speist das Videos-Kapitel zusätzlich daraus;normalize_assessmentsdafür von privat aufpub(crate)angehoben. Neue Regressionstests: EN/DE-Fallback-Guard insrc/output/pdf/tests.rs(fiktive Regel ohneexplanations.rs- Eintrag) sowie zwei neuebik_guide-Tests (manual-review-only Fund erscheint im Videos-Kapitel; unrelated-Kriterium leakt nicht rein). -
Neues
html_conform-Modul: HTML5-Spezifikationskonformität viahtml-conform-Crate, 2026-08-30: neues, standalone Modul (src/html_conform/), das perhtml-conform(crates.io, pure Rust, kein Netzwerk/Subprocess) Browser-artige HTML5-Baumkonstruktion, volle W3C-RelaxNG-Schemavalidierung, Schematron-Co-Constraints sowie Import-Map-/ Speculation-Rules-JSON- und CSP-Enforcement-Prüfung durchführt — deutlich tiefer als der bestehende, unverändert belassenehtml5ever-Parse-Fehler-Check inseo::page_health(validate_html_locally). Läuft als Teil von--full(check_html_conform: full_audit, kein eigener CLI-Flag), nur auf dem Mobile-Viewport-Pass (analog SEO/Mobile/DesignQuality), und fließt in den Score ein (nicht score-neutral wiedesign_quality/ai_transparency): eigenesModuleScoreEntrymit 10 % Gewicht, finanziert durch eine proportionale Kürzung der fünf bisherigen Gewichte (Accessibility 40→36, Performance 20→18, SEO 20→18, Security 10→9, Mobile 10→9). Wiesecurityals eigenständiges Top-Level-Feld aufAuditReport(html_conform: Option<HtmlConformAnalysis>, URL-/Seiten-Ebene statt Viewport-Experience) und außerhalb des Viewport-Blends aufoverall_scoreaufgesetzt (ScoreBreakdown.html_conform_score/ _weight_pct, analogsecurity_score/security_weight_pct).rule_id/messagebleiben opakes kanonisch-englisches Passthrough (html-conforms Regelmenge ist offen, nicht ein kleines Enum, und die Messages sind Drittanbieter-Fließtext) — bewusst kein #406- kind-Enum-Muster, gleiche Präzedenz wiebest_practices::console_errors/vulnerable_libs. Auf einem technischen Setup-Fehler oder HTML-Extraktions-Fehler:checked: false/score: 100(“nicht gemessen”, ausgeschlossen vom gewichteten Overall-Score) statt eines punitiven 0, analog Performance’smetrics_available == 0-Behandlung. Volle JSON/PDF-Anbindung (ModuleBlob.html_conform,HtmlConformPresentation, eigener PDF-Renderer mit ScoreCard/ MetricStrip/AuditTable). Batch-PDF-Narrativ-Arme (batch_report/sections.rs) und URL-Matrix-Spalte bewusst nicht Teil dieser Änderung (Score zählt bereits automatisch in jedes Batch-Aggregat ein) — gleiche Präzedenz wie der akzeptiertecommerce-hat-kein-PDF-Gap. -
Neues
ai_transparency-Modul: C2PA-Bildherkunfts-Check (EU AI Act Art. 50), 2026-07-31: neues, opt-in (--ai-transparency) und score-neutrales Modul, das eingebettete C2PA- (“Content Credentials”-)Manifeste auf<img>-Elementen der geprüften Seite liest und — falls das Manifest eine KI-/algorithmische Erzeugung ausweist (IPTCdigitalSourceTypetrainedAlgorithmicMedia/compositeWithTrainedAlgorithmicMedia/compositeSynthetic/virtualRecording/trainedAlgorithmicData) — einen Manual-Review-Advisory-Fund erzeugt. Bewusst nur Presence melden, kein Versuch, eine sichtbare Kennzeichnung in Bild-Nähe automatisch zu erkennen (gleiches Fragilitäts-Problem wie Chat-Widget-Disclosure-Detection, daher aus dem Scope genommen), und bewusst kein EXIF-Software-Tag-Fallback (C2PA ist der vom AI Act referenzierte Standard-Mechanismus, EXIF wäre eine zweite, unsichere Erkennungslogik). Nur Single-URL-Modus (PipelineConfig.check_ai_transparency = args.ai_transparency && args.url.is_some(), exaktes Muster wiecapture_element_evidence): ein Batch-Lauf würde pro Bild einen Netzwerk-Fetch + C2PA-Parse machen, nur damitbuild_batch_detail()den gesamten Modul-Blob pro Seite ohnehin verwirft (#256). Doppeltes Opt-in: neues Cargo-Featureai-transparency(c2pa = "0.90.3",default-features = false, features = ["rust_native_crypto"]— kein natives OpenSSL, keine HTTP-Client-Features, damit der Reader strukturell nie selbst nachlädt/telefoniert), nicht Teil vondefault(Precedent:pdf-Feature) — plus der Laufzeit-Flag. Fehlt das Feature beim Build, aber ist der Flag gesetzt, brichtsrc/cli/runners.rsscheck_ai_transparency_featurevor Pipeline-Start laut mitConfigErrorab (Rebuild-Hinweis) statt still No-op zu bleiben. SSRF-Härtung (erster Fetch dieses Codebase auf fremde, potenziell durch die geprüfte Seite kontrollierte Origins — anders alsseo::robots/securitymit reinem Same-Origin-Fetch): eigene DNS-Auflösung + IP-Range-Check (privat/loopback/link-local/multicast/IPv4-mapped-IPv6, explizite Range-Checks statt neuerer, ggf. noch nicht überall stabilerIpv6Addr-Methoden) VOR jedem Fetch, plusreqwest::ClientBuilder::resolve()pinnt die Verbindung auf exakt die geprüfte IP (verhindert einen zweiten, nicht erneut geprüften DNS-Lookup beim eigentlichen Connect — DNS-Rebinding-TOCTOU). Manifest-Auswertung nutzt die typisiertec2pa-API (Manifest::find_assertion::<Actions>(labels::ACTIONS)→Action::source_type()/.software_agent(),Reader::validation_state()), kein Blind-JSON-String-Walk — robuster gegen Schema-Drift und Fließtext-False-Positives. Lokalisierung folgt dem etablierten kind-Enum-Muster (#406):finding_message_text(rule_id, en)einzige Textquelle. Neue lokale EnumsAiProvenanceKind/ManifestValidationspiegelnc2pa::DigitalSourceType/ValidationState, damitsrc/ai_transparency/mod.rs(Typen, JSON/PDF-Anbindung) ohne das Cargo-Feature kompiliert — nurimage_provenance.rs(der eigentliche Fetch+Parse) ist#[cfg(feature = "ai-transparency")]-gated; Modul-Registrierung inAuditCatalog,ModuleData- Variante,ExperienceSection.ai_transparency,ModuleBlob/PDF-Dispatch bleiben unconditional (kleinerer Diff als ursprünglich geplant, keine#[cfg]-Streuung über Catalog/Report/Output). Test-Fixtures für den C2PA-Parser sind synthetisch selbst erzeugt zur Testzeit (c2pa::Builder+EphemeralSigner, in ein hartcodiertes 1×1-PNG signiert) statt aus den öffentlichen C2PA-Testdateien (c2pa-org/public-testfiles, CC-BY-SA-4.0) vendored — die enthalten keine eindeutig als KI-generiert gekennzeichneten Samples (nur Adobe-Editing- Konformitätstests von 2022), und eine synthetische Fixture vermeidet jede Lizenz-/ Attributions-Frage. Realer, dabei entdeckter Nebeneffekt:c2paaktiviert unconditional (fest in dessen eigenerCargo.toml, nicht hinter einem seiner optionalen Features)serde_jsonspreserve_order-Feature — durch Cargo-Feature-Unification ändert dasserde_json::Value::Objects Backing-Map projektweit von sortiertemBTreeMapauf insertion-orderIndexMap, sobaldai-transparencymitgebaut wird, unabhängig davon ob der Laufzeit-Flag je gesetzt wird. Betraf 4 bestehende Snapshot-Tests (src/output/sr_audit_json.rs, 3×tests/snapshot_tests.rs), die stillschweigend alphabetische Schlüssel-Reihenfolge voraussetzten — behoben mit einer kleinensort_json_keys-Normalisierungsfunktion vor dem jeweiligenassert_json_snapshot!, nicht durch Snapshot-Neuaufnahme (die Reihenfolge muss unabhängig vom Feature-Set stabil bleiben). Vollständig grün verifiziert:cargo test --all-features --lib(1157 passed),cargo clippy --all-features -- -D warningsauf lib+bins+den beiden betroffenen Test-Targets (ein separates, bereits vor dieser Session bestehendes Dead-Code-Problem intests/wcag_rule_id_inventory.rs/tests/common/— unfertige, uncommittete Arbeit an anderer Stelle — viagit stashals vorbestehend verifiziert, nicht Teil dieser Änderung). Batch-Report-Rollup (analogtemplate_clusters) bewusst nicht Teil dieser Änderung — separates Folge-Issue. -
Kanonische WCAG-Regel-ID-Inventur, 2026-07-31 (#552, tracking #559): schließt eine vorher falsche Annahme —
src/taxonomy/rules.rssRULES(117 Einträge, 85Dimension::Accessibility) wurde fälschlich als vollständige Liste aller Regeln behandelt. Tatsächlich istRULESeine kuratierte Scoring-/Report-Klassifikationstabelle, kein Laufzeit-Register;PAGE_RULESsrule_id(page_rules.rs) ist laut eigenem Doc-Kommentar “used for logging only”,run_if_allowed!s axe_id (engine.rs) ist nurRuleOutcome- Telemetrie. Der tatsächliche Mechanismus hinterViolation.rule_id: eineRuleMetadata- Konstante pro Regeldatei. Neuer, browser-freier Testtests/wcag_rule_id_inventory.rsscanntsrc/wcag/rules/*.rs(RuleMetadata-Literale + die bare*_AXE_ID-Konstanten inaria_roles.rs/widget_rules.rs) undsrc/patterns/*.rs(literale.with_rule_id("...")- Aufrufe in den Pattern-Detection-Modulen Accordion/Modal/TabList/DisclosureMenu, die ganz ohneRuleMetadataauskommen) — reale, verifizierte Zahl: 116 distinkterule_ids, nicht 117/85. Empirisch gegen einen vollen--full --level aaa-Lauf (casoon.de) abgeglichen; zwei reale, vorher unbemerkte Diskrepanzen dabei gefunden: (1)NormalizedFinding.rule_idführt einen zweiten, parallelen ID-Namensraum (a11y.*, austaxonomy::rules::RULES) neben dem hier inventarisierten axe_id-Namensraum — beide sind für dieselbe Regel im JSON gleichzeitig sichtbar, je nach Report-Stelle; kein Bug, aber wichtig für zukünftige Regel-Vergleiche (welchen Namensraum meint man?). (2) Reflow trägt zwei abweichende IDs für dieselbe Prüfung —RuleOutcome.rule_id = "reflow"(Telemetrie-Label inpipeline.rs) vs.Violation.rule_id = "css-overflow-hidden"(REFLOW_RULE.axe_id, tatsächliches Finding). Dabei zusätzlich entdeckt und als eigenes Bug-Issue #560 angelegt (bewusst nicht in #552 mitgefixt, unabhängiges Problem): Kontrast (1.4.3) und Reflow (1.4.10) laufen beide direkt inline inpipeline.rssrun_rules, ohne jeRuleFilterConfig.should_run(...)zu prüfen —[rules] disabled/enabled_onlyinauditmysite.toml(dokumentiertes Feature,src/cli/config.rs:75) hat für diese zwei Regeln aktuell keine Wirkung. Diese Inventur ist die Grundlage für den geplanten Ground-Truth-Detection-Corpus (#553–#558, sieheplans/contrast-a11y-detection-testbed.md), nichtRULES. -
Detection-Corpus-Format + Completeness-Check, 2026-07-31 (#553, #554, tracking #559): baut auf der Regel-Inventur (#552) auf. Neues, browser-freies
tests/common/(geteilter Test-Support, nicht selbst ein Testbinary — Standard-Rust-Idiom):rule_inventory.rs(die #552-Scan-Logik, jetzt wiederverwendbar) unddetection_corpus.rs(ExpectedCase/Expectation/Verdict-Typen + Loader fürtests/fixtures/detection_corpus/<case>.expected.jsonundstructurally_deferred.json— beide Loader liefern eine leere Liste statt zu fehlern, wenn Verzeichnis/Datei noch nicht existiert, da der Corpus bewusst leer startet und erst #556 ihn befüllt).tests/detection_corpus_format_test.rs(7 Tests, Parsing/Loader gegen ein Tempdir, kein echtes Fixture nötig — vermeidet unverifizierte Ground-Truth-Werte vor #556).tests/detection_corpus_completeness.rsdiffed die 116 Regel-IDs gegen die im Corpus referenzierten IDs +structurally_deferred.json, meldet covered/deferred/gap ohne bei niedriger Abdeckung zu scheitern (Startzustand aktuell: 0/0/116 — erwarteter Rückstand, kein Regressions-Fehlschlag), schlägt aber hart fehl bei einem echten Autoren-Fehler (referenzierterule_idexistiert gar nicht in der Inventur, oder eine Regel ist gleichzeitig abgedeckt und alsstructurally_deferredmarkiert). -
Runemark-Terminalpräsentation + Batch-Progress-Adapter, 2026-07-30 (#529, #530): #529 ersetzt den bisherigen
colored/comfy-table-Renderer (src/output/cli.rs, gelöscht) für--format tabledurch eine dünne Präsentationsschicht aufrunemark(eigenes, auf crates.io veröffentlichtes Schwester-Crate wierenderreport; Version live gegen crates.io geprüft: 0.2.0 ist die aktuell veröffentlichte Version, nicht geraten). Neuessrc/output/terminal.rsmappt die bereits existierende, von JSON/PDF geteilte Präsentationsschicht (ReportViewModel/BatchPresentation— bewusst wiederverwendet statt neu gebaut, beide sind nicht hinterfeature = "pdf"gegated, also für--format tablein jeder Feature-Kombination verfügbar) aufrunemark::report::Report(Console,Verdict,Metric,FindingGroup,NextStep,RenderOptions). Neuer--color {auto,always,never}-Flag (ColorPolicy); Terminalbreite viaconsole::Term::stdout()(bereits transitive Dependency vondialoguer, jetzt direkt).console_for()erzwingtNever, wenn das Ergebnis in eine Datei geschrieben wird (--output), außer bei explizitem--color always— verhindert ANSI-Bytes in gespeicherten Report-Dateien.comfy-tablekomplett entfernt (0 verbleibende Referenzen);coloredbleibt, da an vielen anderen CLI-Stellen (Banner, doctor, sitemap-suggest) weiter genutzt — bewusst nicht im Scope dieses Issues, das Issue verlangt nur “remove only once nothing requires it”. #530 ersetzt die direkteindicatif::ProgressBar-Nutzung inrun_batch_mode(src/cli/runners.rs) durchBatchLifecyclePresenter(neu,src/cli/batch_lifecycle.rs), einen dünnen Adapter überrunemark::ProgressSink(TerminalProgress/SilentProgress,progress-Feature von runemark, ziehtindicatifjetzt transitiv statt direkt — eigeneindicatif-Dependency entfernt). Neuer--progress {auto,always,never}-Flag (ProgressPolicy), unabhängig von--quiet(das weiterhin vollständig unterdrückt).run_concurrent_batchs Signatur/Concurrency-Algorithmus unverändert — nur der Callback-Body inrunners.rsruft jetztpresenter.advance/notice_errorstatt direkt die Progress-Bar zu berühren. Realer, dabei entdeckter Bug behoben: Banner-/Plan-/ Diagnose-/Ergebnis-/Verdict-Zeilen insrc/cli/plan.rsundrun_batch_modewaren durchgehendprintln!(stdout), nur von--quietgegated, nicht vom Output-Format — ein Batch-Lauf mit--format jsonohne--output/--quietmischte Klartext in den JSON-Payload auf stdout. Mechanischer Fix: alle diese Zeilen jetzteprintln!(stderr) bzw. laufen für die eigentliche Batch-Lifecycle (Sitemap-/Crawl-Diagnose-Einzeiler, Results-Zeile, finaler Verdict) durchpresenter.notice()/presenter.finish_verdict().print_verdict()(auch von Single-Mode genutzt) bleibt als eigenständige Funktion bestehen, aber jetzteprintln!-basiert — behebt dieselbe Bug-Klasse auch für Single-Mode-JSON-Läufe. Zweiter, von der ersten Live-Verifikation aufgedeckter Leck gefunden und gefixt: der globaletracing_subscriber::fmt()insrc/main.rshatte keinen expliziten.with_writer(...)und schrieb dadurch (tracing-subscribers eigener Default) selbst auf stdout — eintracing::warn!während eines Batch-Laufs (z. B. ein Page-Timeout insrc/audit/batch.rs:164) landete dadurch weiterhin, ANSI-gefärbt, vor dem JSON-Payload auf stdout und brachjq. Jetzt.with_writer(std::io::stderr)+.with_ansi(...), Letzteres an dieselbe--color-Policy gekoppelt (nicht an tracing-subscribers eigene, unabhängige Auto-Erkennung). Nach diesem Fix erneut live verifiziert (Timeout-Fall gezielt reproduziert): stdout bei--format jsonbleibt bei einem Batch-Lauf ohne--quietjetzt auch bei einem Page-Timeout sauberes, vonjqvalide geparstes JSON;--progress never --quieterzeugt exakt 0 Byte auf stderr;--color alwayserzwingt ANSI auf stderr;report-lintauf dem Ergebnis-JSON liefert keine Findings. -
Contrast pixel-sampling coverage + neues
design_quality-Modul, 2026-07-30 (#527, #528): #527 behebt eine reale Lücke bei WCAG 1.4.3:build_sample_tasks(src/wcag/rules/contrast.rs) sampelte bisher nur, wenn der Hintergrund unsicher WAR und das CSS-Ratio bereits durchfiel — ein Element mit optisch plausiblen CSS-Farben, dessen gerendertes Ergebnis durch Opacity-Stack, Blend-Mode oder ein echtes<img>hinter dem Text reduziert wird, wurde nie gesampelt und blieb komplett unsichtbar (live an casoon.de bestätigt:finding_count: 0trotz sichtbar fehlschlagendem Hero-Bereich). Fix: die Sampling-Bedingung sampelt jetzt bei jedem unsicheren Hintergrund unabhängig vom CSS-Ratio (mitMAX_SAMPLE_TASKS = 60-Deckel, größte Fläche zuerst);styles.rss Ancestor-Walk erkennt „unsicher” jetzt zusätzlich beiopacity < 1,mix-blend-mode/background-blend-modeund einem überlappenden<img>/positionierten Overlay-Geschwister (begrenzt auf die ersten 3 Ancestor-Ebenen). Ein dabei gefundener echter Duplicate-Finding-Bug wurde mitgefixt: dieselbe Selector/Regel-Kombination konnte durch unterschiedliche Sampling-Ergebnisse zwischen Desktop- und Mobile-Pass gleichzeitig als bestätigter Violation UND als NeedsReview-Warning auftauchen —merge_wcag_violations(src/audit/pipeline.rs) unterdrückt jetzt Warnings für jede Selector/Regel-Kombination, die bereits als Violation vorliegt. Neue Fixturetests/fixtures/opacity_overlay_contrast.html+test_opacity_overlay_contrast_pixel_sampling. #528 fügtdesign_qualityals neues, opt-in (--design-quality, bewusst noch nicht Teil von--full) und score-neutrales Modul hinzu: heuristische UX-/Lesbarkeits-Hinweise (Overflow-Clip bei positionierten/interaktiven Elementen, Zeilenlänge, Zeilenabstand, langer Großbuchstaben-Text, layoutwirksame CSS-Transitions) aus der bereits offenen CDP-Seite, ein einzigerpage.evaluate()- Durchlauf für alle Textregeln. EigenerDesignQualityFinding-Typ (nichtwcag::types::Violation) garantiert Score-Neutralität durch Konstruktion — es gibt nie einenpush_indicator-Aufruf/ModuleScoreEntry, daher berührt kein CodepfadAccessibilityScorer/den gewichteten Overall-Score; per Integrationstest verifiziert (report.accessibility.score/grade/certificatebyte-identisch mit und ohne Modul). Layout-Transition-Regel erkennt Transitions nicht selbst neu, sondern projiziertperformance::animations-Funde (auf layoutwirksame Properties gefiltert) als Advisories — vermeidet einen zweiten unabhängigen Erkennungspfad und damit Duplicate Findings by construction. Lokalisierung folgt dem etablierten kind-Enum-Muster (#406):finding_message_text(rule_id, level, en)ist die einzige Textquelle, Analyse ruft miten=true(JSON kanonisch Englisch), PDF-Builder mit der Lauf-Sprache. NeuesExperienceSection.design_quality- Feld, volle JSON/PDF-Anbindung (ReportModule,ModuleBlob,ModuleDetailsBlock, eigener PDF-Renderer ohne ScoreCard). Cache-Signatur auffmt=12gebumpt. -
Report Quality Layer v1.2 — Phase 5: visuelle Prüfpipeline, 2026-07-16 (#510, tracking #512): schließt die zuvor als offen dokumentierte Lücke (siehe vorheriger Eintrag unten, “partial”). Statt einer gespeicherten Pixel-Diff-Baseline (Font-Rendering/Anti-Aliasing würde das über verschiedene Maschinen/CI-Runner hinweg flaky machen) zwei neue, deterministische Same-Run-Prüfungen in
src/output/pdf/tests.rs: (1)test_dual_viewport_performance_renders_two_gauges_not_a_flat_strip— Struktur-Regressionswächter für den in #510 konkret genannten historischen Fall (die “umgebrochene Dual-Viewport-Zelle”): prüft im Typst-Quelltext (nicht Pixeln), dass der Desktop/Mobile-Performance-Vergleich weiterhin als zwei eigenständigeGauge-Komponenten in einem 2-Spalten-Gridrendert (type: "gauge"≥ 2×, eigenelabel: "Desktop"/label: "Mobile") statt in die vormalige flache “Desktop 85 · Mobile 67”-Textzeile zurückzufallen — mit einem neuendual_viewport_performance(desktop_score, mobile_score)- Test-Helper, derreport.performance(mobil) +report.dual_viewport.desktop.performance(Desktop) konstruiert, dem einzigen Weg,PerformancePresentation.desktop/.mobilebeide zu befüllen. (2)test_representative_fixture_pages_are_not_blank_and_stay_within_page_budget— rastert eine angereicherte Fixture (WCAG-Funde, Dual-Viewport-Gauges, Tech-Stack-Tabellen, gedrosselte Netzwerk-Tabelle; deckt damit Cover/Scorekarten/Tabellen/Findings/Methodik/technische Kennzahlen aus #510s Abnahmekriterium ab) auf Technical-Level und prüft Nicht-Leere UND ein Seiten-Budget (60 Seiten, erkennt einen Reflow-Ausreißer, ohne subjektive Layout-Qualität pixelbasiert zu beurteilen — das bleibt bewusst Aufgabe desreport-critic-Skills, #509). Bei einem Fehlschlag werden alle rasterisierten Seiten nachtarget/pdf-visual-debug/<grund>/kopiert (Tempdir-Inhalte wären sonst vor jeder manuellen Inspektion gelöscht). Realer, zuvor unbemerkter Gap gefunden und gefixt: derpdf-smoke-CI-Job installiertepoppler-utilsnie — jederpdftoppm/pdftotext- gated Test (alle Blank-Page-/Visual-Tests aus Phase 5 sowie die älterenpdftotext-basierten Content-Traceability-Tests) übersprang sich in CI seit ihrer Einführung selbst still (find_executable("pdftoppm")→None) statt zu laufen — nie als rotes CI sichtbar, weil ein übersprungener Test nicht fehlschlägt..github/workflows/ci.yml’spdf-smoke-Job installiertpoppler-utilsjetzt perapt-getund lädttarget/pdf-visual-debug/bei einem Fehlschlag als Build-Artefakt hoch (actions/upload-artifact@v4,if: failure()) — schließt #510s Abnahmekriterium “erzeugte Artefakte sind in CI nachvollziehbar”. -
Report Quality Layer v1.2 — Phase 3: Feedback-Korpus, 2026-07-16 (#511, tracking #512): neues
tests/regression_corpus/*.json(16 Einträge, ein File pro bestätigtem Fall) — Format{id, category: Invariant|Semantic|Completeness|Explanation|Visualization, problem, evidence, expected, regression, counter_examples[], status: resolved|known_gap}. Deckt das Startkorpus aus Issue #511 ab (Score-Mismatch, kritisch-als-Label-für-kritisch+hoch, Score ohne Skala, Zähler ohne Scope, vorhandene Daten ohne Reportnutzung, Metrikstreifen-Umbruch, Batch-Klassifikation aus falscher Score-Basis) plus die in dieser Session bestätigten konkreten Fälle: die vier Lokalisierungs-Leaks (SEO-Details, Tech-Stack-Severity, TechCategory-Debug-Format, renderreport-Komponenten-Default-Labels) und denviolated_rule_count-Scope-False-Positive aus Gruppe C — allestatus: resolvedmit Verweis auf ihren jeweiligen Regressionstest. Vier weitere, vomreport-critic-Skill-Dry-Run gegen echte Batch-/Single-Reports gefundene, in dieser Session aber nicht behobene Fälle (score_area_for_finding-Substring-Fehlklassifikation,is_generic()-Linktext-Substring-False-Positive, Impact-vs.-Reach-Widerspruch in Batch-top_actions,{:?}-Debug-Leak + Truncating-Division inmanagement_risks[].rationale) sindstatus: known_gap— bewusst dokumentiert statt stillschweigend fallengelassen. Neuertests/regression_corpus_contract.rsvalidiert nur die Korpus-Form selbst (Pflichtfelder, Enum-Werte, eindeutige/dateiname-passendeid, nicht-leerecounter_examples,resolved⇒ nicht-nullregression) — führt die referenzierten Regressionstests nicht erneut aus..gitignores pauschales*.jsonbekam ein!tests/regression_corpus/*.json-Whitelist-Eintrag (gleiches Muster wietests/lint_fixtures/). -
report-lint False-Positive-Fixes aus report-critic-Eval, 2026-07-16 (#507-Nachbesserung): der Eval-Lauf fand zwei echte False Positives in
src/lint/checks.rs, an einem realen Batch-Report bestätigt (reports/casoon-batch-en301549.json— vorher 2 Findings, jetzt 0): (1)check_grade_and_certificatekanntegate_certificate_by_risk(src/audit/normalized.rs:515) nicht — ein wegen Risk=High/Critical/legal_flags/ blocking_issues absichtlich herabgestuftes Zertifikat (“EINGESCHRÄNKT”/“NICHT BESTANDEN”) wurde fälschlich als Score-Mismatch gemeldet. Jetzt akzeptiert der Check jeden Wert, dengate_certificate_by_riskfür die gegebenen Risk-Eingaben legitim produzieren könnte (risk_gate_inputs/acceptable_certificates, neue Helper). (2)violated_rule_count(global eindeutige Regeln) vs.severity_counts.total(Summe der pro-Seite-eindeutigen Regel-Zeilen) sind auf Batch-summary-Ebene strukturell verschiedene Aggregationen, die nur zufällig für Single-Reports übereinstimmen — der Check verglich sie fälschlich auf exakte Gleichheit. NeueScopeRelation-Unterscheidung:Exactfür Single-Reports und jede einzelne Seite,AtMost(global ≤ Summe) nur fürsummaryeines Batch-Reports. 5 neue Regressionstests insrc/lint/checks.rs. -
Lokalisierungs-Nachbesserung aus report-critic-Eval, 2026-07-16: der report-critic-Eval-Lauf (siehe Skill-Eintrag unten) fand reale Lokalisierungslecks — deutsche Wörter ohne Umlaut/ß, daher vom bestehenden Guard-Test unentdeckt. Gefixt:
build_seo_details(src/output/builder/single/module_details.rs) komplett durchlokalisiert (meta_tags, identity_facts, page_profile_facts, heading/social/technical/tracking_summary, SchemaExtracted- Textbausteine,signal_rows-Rating — ~50 Stellen);Severity::label()(immer Deutsch) an zwei weiteren PDF-Stellen (detail_modules/indicators.rs,detail_modules/overview.rs— dort war ein drittes vermeintliches Vorkommen tatsächlichBudgetSeveritymit eigenen, bereits sprachneutralen “Error”/“Warning”-Labels, kein Bug);single_report.rs(“Absprungrate” im EN-Zweig selbst falsch übersetzt, “Vorkommen” hartcodiert im Format-String). Nebenbefund:{:?}- Debug-Leck beitech.category(TechCategoryhatte keinelabel()-Methode) — jetzt behoben. Neuer Regressionstesttest_seo_details_english_locale_has_no_known_german_leaks(src/output/pdf/tests.rs) — die erste EN-Locale-Prüfung, diebuild_seo_detailsüberhaupt mit echten SEO-Daten ausführt (pdf_fixture_report()allein trägt kein SEO). Wichtige Methodik-Korrektur dabei entdeckt:--debug-typ-Dumps enthalten die komplette renderreport-Komponentenbibliothek (include_str!), nicht nur den tatsächlich gerenderten Content — ein String-Vorkommen im Dump beweist nicht, dass er auf einer Seite erscheint (kann aus einer nie instanziierten Komponentendefinition stammen). Führte zu zwei renderreport-Fixes (v0.2.36, siehe unten) und einer Ergänzung imreport-critic-Skill. -
renderreport v0.2.36:
dominant-issue-spotlightundseverity-overview(zwei von auditmysite aktuell ungenutzte Komponenten) hatten deutsche Labels fest im Typst-Template (label-text("Empfehlung")etc.) statt sie wie alle anderen Komponenten als Datenfeld vom Rust-Aufrufer zu bekommen — kein Live-Bug (unbenutzt), aber Inkonsistenz mit dem etablierten Muster (Rust übergibt immer schon lokalisierte Strings, Templates selbst haben keine Sprachlogik). Beide Templates nehmen jetzt optionalelabel_*-Datenfelder mit deutschen Defaults (data.at(key, default: "...")) an;DominantIssueSpotlight/SeverityOverviewin renderreport haben neuewith_labels(...)-Builder analog zuCoverPage. Rein additiv, keine Verhaltensänderung für bestehende Aufrufer.cover_page.typ’s deutscher Fallback-Wert fürmodules_labelbewusst unverändert gelassen — auditmysite setzt dieses Feld immer explizit für beide Sprachen, der Fallback ist unerreichbar. -
Neuer Skill
report-critic(.claude/skills/report-critic/SKILL.md, #509): evidenzgebundene KI-Kritik eines fertigen Reports (JSON +--debug-typ-Text) gegen Widersprüche, fehlenden Scope, unbelegte Schlussfolgerungen, fehlende Maßnahmen-Verknüpfung, textsichtbare Layout-Artefakte — ergänzt (ersetzt nicht)report-lint. Per skill-creator-Eval-Loop getestet (3 Testfälle, mit/ohne Skill, echter 1.1 MB Batch-Report von casoon.de): beide Konfigurationen fanden durchgehend reale Bugs (Substring-Klassifikations-Bug in zwei unabhängigen Modulen,Some(84)-Debug-Leck, Impact-vs-Reach-Widerspruch in einer Aggregations-Tabelle, u.a.) — der Skill bringt vor allem konsistente Ausgabestruktur, nicht zusätzliche Fähigkeit gegenüber einem bereits sehr kompetenten Baseline-Agenten. Eval-Workspace unter.claude/skills/report-critic-workspace/(nicht committet, lokal). -
Report Quality Layer v1.2 — Phase 5 (partial): visual PDF smoke checks, 2026-07-16 (#510, tracking #512): correction to the original Phase 5 plan — rasterization via
pdftoppmwas not greenfield;src/output/pdf/tests.rsalready had twopdftoppm-gated smoke tests (find_executable("pdftoppm")skips gracefully when unavailable, matching CI’spdf-smokejob which runscargo test --features pdf_test). Extended that existing infra rather than building a newxtask: apng_luma_std_devhelper (grayscale pixel std-dev via the already-presentimagecrate) plusrasterize_pages, backing two new tests —test_single_pdf_technical_pages_are_not_blank_when_pdftoppm_is_availableandtest_batch_pdf_pages_are_not_blank_when_pdftoppm_is_available(batch PDF rasterization had no test at all before this) — flagging a near-solid-color page (missing font/asset → blank render) without exact pixel-diff, which is deliberately avoided: font hinting/anti-aliasing differs across machines/CI runners, so an exact-match baseline would be flaky by construction. Not yet done, open design question: the plan’s “visual diffs for stable layout regions” (e.g. cover skeleton) needs a real decision on baseline storage/regeneration workflow and tolerance before implementing — deferred rather than guessed at. Overflow/cut-content/misalignment detection and the semantic/ visual AI judgment pass both still route to #509 as originally planned. newtests/coverage_matrix.rs— an#[ignore]-gatedexport_coverage_matrixtest (same pattern asoutput::builder::tests::export_all_interpretations) walks everyAuditCatalog::standard()module and counts literal id() occurrences across four surfaces (src/output/pdf/**,docs/OUTPUT_CONTRACT.md, both JSON schemas,tests/**+ fixture dirs), writingreports/coverage_matrix.jsonfor human review. Deliberately not a CI gate — a substring count can false-negative on indirection and false-positive on a common word (confirmed: the tool’s own doc-comment mentioning “commerce” counts as one of commerce’s “fixture references”). Only one hard, always-true assertion ships (every_catalog_module_has_a_unique_nonempty_id— a tripwire on the catalog itself, not on coverage outcomes) rather than “every module has PDF coverage”, which would immediately fail today. Real gap the first run surfaced:commercehas 0 PDF references and 0 references anywhere undertests/outside its own module directory — the module has JSON output (src/output/module.rs,src/output/json.rs) but no PDF rendering and no cross-cutting test coverage, despite CLAUDE.md recording it as “COMPLETE, alle Slices 1-4 gemergt”. Not fixed as part of #508 (out of scope — #508 is the detection tool, not a mandate to close every gap it finds); flagged here for a deliberate decision on whether that’s an intentional Studio/JSON-only scope or a real oversight.#509/#510/#511(AI critic, visual PDF pipeline, feedback corpus) are planned but not started. -
Report Quality Layer v1.2 — Phase 2: deterministic report-lint, 2026-07-16 (#507, tracking #512): new
src/lint/—lint(report: &serde_json::Value) -> LintReportruns four registry-driven (#506) checks with zero network/Chrome dependency, each producing aLintFinding{check_id, evidence_path, expected, actual, severity}: (1)summary.score/summary.overall_scorealias consistency plus single-report summary-vs-page cross-check (the “18/100 vs 20/100” corpus shape); (2)grade/certificatere-derivable fromoverall_scorevia the same sharedLETTER_GRADE/CERTIFICATEBandSets the production scorer uses, checked on bothsummaryand every page; (3)severity_counts/occurrence_counts.totalequal the sum of their four severity fields, andviolated_rule_count/violation_countmatch the corresponding scoped total (the “Zähler ohne Scope” corpus shape); (4) the report’s ownmetric_contextblock matches whatREGISTRYcurrently generates (catches a stale/cached or hand-edited report). New CLI subcommandauditmysite report-lint <file> [--fail-on low|medium|high|critical](defaulthigh) prints findings and returns a non-zero exit code (viaAuditError::ConfigError, exit 3) when the worst finding meets or exceeds the threshold — verified end-to-end against a hand-built broken report before considering this feature done, not just via unit tests.registry::json_path_candidates(moved from a private test-only helper intests/registry_contract.rstosrc/registry/paths.rssolintand the contract test share one implementation) had a latent bug caught while writing paths.rs’s own dedicated unit tests: splitting on" and "before trimming turned prose like “…score and nested dimension scores” into a second bogus path candidate (“nested”) — fixed by dropping the" and "split entirely (only" / "ever separates two real paths in this codebase’sjson_pathtext;take_whilealone already stops at the first invalid character, which discards trailing “ and …” prose). Added a 5th check: everyREGISTRYentry’sdocs_urlis a well-formed<path>#<anchor>reference (shape only, no filesystem access — a released binary has no guaranteedocs/exists alongside it; the deeper anchor-resolution check stays intests/registry_contract.rs, which only needs to hold in the dev/CI checkout).tests/lint_fixtures/*.json(clean single, 3 broken variants covering score-alias/grade/batch-certificate mismatches) plustests/report_lint_tests.rsspawn the compiled binary end-to-end viaCARGO_BIN_EXE_auditmysiteand assert on exit code + finding check-ids — these fixtures double as the seed for #511’s regression corpus. No new CI job was added: since this test file has no network/Chrome/pdf-feature dependency, it’s already exercised by the existing unscopedcargo testin thecheck/check-all-featuresjobs. Added a 6th, narrowly-scoped PDF-traceability check instead of the originally-sketched “does any number in the PDF appear anywhere in the JSON” scan (rejected: real false-positive risk from dates/page counts/unrelated percentages). New optional--typst-source <path>onreport-lint(the--debug-typTypst source for the same report); when given,check_pdf_certificate_traceabilitycomputes the certificate token the JSON’soverall_scoreimplies via the same sharedCERTIFICATEBandSetthe PDF itself uses, and checks that exact token is present in the Typst text — presence-only (not “no other certificate word may appear”, since a legend explaining the band system may legitimately mention other tokens),Severity::Low(advisory, never breaches the default--fail-on highon its own).lint()’s signature grew a secondOption<&str>parameter for the Typst text. Two.typfixtures added undertests/lint_fixtures/; a first draft of the “broken” fixture accidentally spelled the certificate word out in its own comment (“SEHR GUT” contains “GUT” as a substring) and silently passed — caught only because the CLI integration test asserted on the actual finding appearing, not just the exit code, which is why report-lint’s own test fixtures need their negative-case text double-checked for accidental substring self-matches.#508–#511(coverage matrix, visual PDF pipeline, AI critic, feedback corpus) are planned but not started. -
Report Quality Layer v1.2 — Phase 1: canonical metric registry, 2026-07-16 (#506, tracking #512): new
src/registry/(MetricSpec/BandSet/MetricKind/Direction/Scope/Aggregation,REGISTRYconst table) gives every specialized number one machine-readable definition instead of scattered renderer/doc logic. Seeded 1:1 fromsrc/output/json.rs’s former hand-writtenmetric_context()vec —metric_context()now derivesscore_definitions/count_definitionsfromREGISTRYinstead of the other way around, with the samefield/unit/meaningtext (zero JSON output change, verified against snapshot/schema/consistency test suites).docs/OUTPUT_CONTRACT.mdgained a## Metricssection with one<a id>anchor per registry entry;tests/registry_contract.rs(mirrorstests/parity_contract.rs’s “contract file + test” shape) checks unique ids,docs_urlanchors resolve,reviewed_atparses as a date, andjson_pathresolves againstdocs/json-report.schema.json/docs/json-batch-report.schema.json. Phase 1 complete (all 7 migration steps): every one of the ~19 independent score→label/grade definitions found across taxonomy, PDF renderers, and module-specific label functions now references a namedBandSetinsrc/registry/bands.rsinstead of re-coding thresholds —FIVE_BAND(90/75/60/40 words),FIVE_BAND_LETTERS(same cutoffs, A–F),LETTER_GRADE(90/80/70/60, A–F),SECURITY_GRADE(90/80/70/60/50, A+–F),BATCH_GRADE(95/90/80/70/60, A+–F),CERTIFICATE(90/75/60/40, SEHR GUT…UNGENÜGEND — was independently re-implemented in bothaudit::scoring::calculate_certificateand the PDF’scover::batch_certificate_labelbefore this migration),COVER_PHRASE/SCORE_RANGE(90/75/60/40, sentence variants),MEDAL(90/80/60, terminal-table GOLD/SILVER/BRONZE/FAILED),BAR_COLOR_BAND(90/80/70/50, terminal color only), andSEO_BAND(90/70/55/35 — SEO’s own family, deliberately kept distinct, not collapsed intoFIVE_BAND). No threshold values changed;output::cli::colorize_gradewas deliberately left untouched (keys off an already-resolved grade letter, no threshold to register). Found and flagged but not fixed (out of scope for #506):performance::scoring::PerformanceGrade::emoji()/.label()/Displayare dead code — never called anywhere reachable insrc/(JSON serializes the enum variant name directly; the PDF renders scores via the now-migratedscore_band_label/score_range_labelinstead), so the emoji never actually reaches a report despite existing in source.#507–#511(report-lint, coverage matrix, visual PDF pipeline, AI critic, feedback corpus) are planned but not started. -
BFSG / EN 301 549 mapping annex, 2026-07-15 (#en301549):
src/wcag/en301549.rs— canonical 50-entry WCAG 2.1 A/AA ↔ EN 301 549 (chapter 9, “Web”) clause table,derive_annex/derive_batch_rollupas pure projections overNormalizedFinding(nothing new stored onNormalizedReport, no cache-signature change). Four-way scope split per clause: violations found / no violations in automated scope / manual review required / (chapter-level, not per-clause) out of audit scope.screen_reader/bfsg.rsreduced to a thin wrapper; the legally-unverified"§12 Abs. 1"citation stays local there, deliberately not propagated. JSONen301549_annexalways emitted (PageDetail, single + batch) plus a batchUnifiedSummary.en301549_rollup; the PDF appendix only renders behind the new opt-in--annex en301549flag (“Zusatz”, not default-on). Disclaimer text (DE/EN) is a scope-of-testing disclosure only — no statutory citation, no conformity claim — reusing this project’s existing “manual audit with assistive technologies (screen reader, keyboard navigation)” wording rather than inventing new phrasing. -
Plain-language content in the existing PDF, 2026-07-15: no separate report variant — the Chapter 02 finding card gained a plain-language lead-in (
customer_description+user_impact) between the header and the QA-meta block (previously not rendered there at all, not just misordered).finding_group_from_normalized’s no-RuleExplanationfallback no longer leaks raw canonical-Englishf.descriptioninto German reports. Part-1 divider reframed as dual-audience (“Inhaber, Entscheider und Entwickler”). -
Journey × Commerce deepening, 2026-07-14/15: form-error journey now groups required fields into up to 3 per-form candidates (was one page-wide candidate) and a
PURCHASE_FINAL_HINTSdeny-list guarantees a purchase-final button (e.g. “Jetzt kaufen”) is never a synthetic-click target. New commerce-aware journeys on a detected shop’s product-detail page under--interactive full: add-to-cart feedback (SC 4.1.3) and quantity-stepper operability (SC 2.1.1/4.1.2).CommercePageKind::Cart/::Checkoutremoved entirely (breaking JSON change) — this tool has no cross-page session/cart state, so a cart/checkout URL reached cold is almost always empty or redirects before rendering anything a page-kind-gated heuristic could act on; confirmed no reference in the siblingauditmysite_studiorepo before landing. -
WCAG coverage + correctness sweep, 2026-07-14: new rules 1.3.2 Meaningful Sequence, 3.3.7 Redundant Entry, 2.4.11/2.4.12 Focus Not Obscured, 2.2.2 Pause/Stop/Hide (automated WCAG-AA count now 36/50, up from 33). Fixed three known-defective rules:
focus_visible_css.rs(never fired in production — missing evidence selector demoted every finding to a warning),focus_visible.rs(dead AX-treetabindexread, removed),non_text_contrast.rs(mistagged/ dead, replaced by a real CDP-basednon_text_contrast_css.rs). Closed remaining #406 localization gaps (Dark Mode, Tastatur-Journey,expected_impact/complexity_reason) and several report-wording/readability fixes across Chapters 01–03 of the single report. -
Evidence-Grade Findings (single report only) + Template-Root-Cause-Dedup (batch only), 2026-07-14: Single-report finding cards now embed a cropped element screenshot (
src/accessibility/element_capture.rs, gated on single-URL mode viaPipelineConfig.capture_element_evidence, capped at 12 crops/report, contrast findings excluded by construction), a ≤3-level DOM path, and computed contrast-ratio evidence (ViolationEvidence::computed,OccurrenceDetail.evidence: Vec<ViolationEvidence>— new additive JSON field,docs/json-report.schema.jsonupdated). Batch reports gain verified template-level clustering (src/audit/template_dedup.rs): findings sharing an identical(rule_id, normalized selector)fingerprint across ≥3 pages / ≥60% coverage become aTemplateCluster(confirmedwhen the HTML-snippet shape also matches,likelyotherwise — decision-action wording only upgrades forconfirmed), surfaced additively inUnifiedSummary.template_clustersand the batch PDF. Both features are additive/JSON-safe (screenshot bytes are#[serde(skip)], never touch cachedreport.json). Fixed two pre-existing binary-test regressions surfaced by running the fullcargo test --features pdf/--no-default-featuressuites (not covered bycargo test --lib): a stalenon_text_contrast→non_text_contrast_cssrename reference and a staleKNOWN_EXCEPTIONSentry intests/wcag_coverage.rs. -
Product-Grade PDF-Redesign (Single-Report, PR feat/report-product-redesign): Cover als komponiertes Dashboard (dominanter Overall-Score + Notenband-Phrase + Modul-Gauge-Strip); Management-Sicht mit Severity-Zählern, Spider-Radar „Qualitätsprofil” und Stärken/Optimierungs-Cards; jedes Modul ein eigenes Level-2-Kapitel mit Magazin-Opener + Kernaussage-Zeile (#15); AI-Visibility + Content-Visibility + Source-Quality zu einem Kapitel „KI & Vertrauen” zusammengeführt; Maßnahmenplan als Action-Cards gruppiert nach Problem-Ebene (systemisch/lokal, ohne Zeit/Aufwand); Ursachen-Verteilung als Bar-Chart; ToC auf Top-Ebene (depth 2); moderne randlose Tabellen; durchgängiges 4-Farben-Gesetz in
src/output/pdf/design.rs(score_color/severity_color, Schwellen 75/40); kein „/100”, kein A–F-Grade (Band-Label viascore_band_label), keine Emoji. renderreport 0.2.26 (komponiertecover-page, echter Spider-Radar, randloseaudit-table, de-emoji’te Callouts, sticky Headings/Komponenten-Titel gegen verwaiste Überschriften). JSON-Fix: Cache-Hit-JSON emittiert jetzt den vollendetail.modules-Blob (zuvor leer, da normalized-only-Pfad). -
Semantic-Eval komplett entfernt: Modul
src/semantic_eval/(Fastembed + Mistral), CLI-Flag--no-semantic-eval/--semantic-eval,[semantic_eval]-TOML-Sektion,fastembed-Dependency +semantic-eval-Cargo-Feature, TypAdvisoryFindingund das Feldadvisory_findings(ausNormalizedReport/AuditReport/JSON sowie den PDF-Advisory-Sektionen).audit_signatureenthält keinsemantic-Segment mehr (Cache invalidiert einmalig). -
Scoring-Korrektheit + Report-Lesbarkeit (PR fix/perf-relative-weight-cap): relativer Weight-Penalty-Cap (≤70 % der Vitals-Basis, schützt Low-Base-Seiten vor 0); renderreport 0.2.23 (Progress-Arc-Gauges + feste Label-Box, keine Cover-Überlagerung); Customer-Passagen ohne Jargon-Duplikat/Meta-Prefixe; Cover-Label „N Accessibility-Befunde” (Scope explizit, WCAG-only); Vuln-Detektion Lodash↔Underscore via
_.runInContext; #406-Leaks (search_experience-Komponenten + Warnungen re-derived); Pluralisierung „1 Schema”;compact_html(data-URIs → „data:…“, Zeilenhöhen); leere „Befunde nach Ursache”-Trenn-Seite gefüllt; kurze Indikator-Module (Best Practices/Tech-Stack) per Divider gepackt statt je eigene Fast-Leerseite. -
Cache-Korrektheit (PR #458, #404/#405): voller
AuditReportwird gecacht (report.json, Screenshots gestrippt), Cache-Hits rendern originalgetreu statt über das verlustbehafteteto_audit_report;screen_reader_audit(#[serde(skip)]) wird viahydrate_cached_reportaus dem AXTree neu gebaut.NormalizedReport-Felder mitskip_serializing_ifhaben jetzt#[serde(default)](Round-Trip-Blocker behoben — der Cache lud nie). Verdikt immer auscached.audit.persist_artifactsläuft nach der Canonical-Perf-Adoption (audit_pagegibtSnapshotDatazurück).audit_signatureenthältlang; korrupter Cache → Miss + Warnung. -
Report-Qualität (PR #459, #446): Security/SEO/Page-Health geben bei leerer Findings-Sammlung eine „keine Auffälligkeiten”-Bestätigungszeile aus (
pdf-section-clean) — „geprüft & sauber” vs. „nicht geprüft” unterscheidbar. -
Scoring-Korrekturen (PR #460, #455/#456/#457): DOM-Größe als degressiver Penalty (max 35) statt hartem 59-Cap; Throttled-Profile bekommen die Headline-
content_weight(keine Slow3G>Fast3G-Inversion); Risk-Breadth-Pfad von Critical-Occurrences entkoppelt (legal_flags >= 3),driven_by/Summary spiegeln den echten Auslöser (Breadth vs. Volumen). -
Audit-Qualität (PR #454): Lokalisierungs-Fixes (Security-CSP, WCAG-Findings, SEO-Heading kanonisch Englisch); Scoring-Entsättigung (DOM-Cap >6000/>10000, Accessibility-Wurzelkurve ab Penalty 70, Mobile-Soft-Floor, Risk=Critical nur bei systemischer Exposition #250); Core-Web-Vitals-Messkorrektheit (CLS Session-Window, LCP+TBT aufs Lade-Fenster begrenzt,
MeasurementContext::LabThrottledMobilekennzeichnet gedrosselte Headline-Vitals im JSON). -
Lokalisierungs-Architektur (#406): JSON kanonisch Englisch, nur PDF mehrsprachig. Analyse backt Englisch, PDF-Präsentation leitet ab (kind-Enum-Muster). Siehe Abschnitt „Lokalisierungs-Architektur”. Plus Audit-Finding-Fixes (#442–#452, #411, #447, #449) — PR #453.
-
Catalog-Refactoring (Phase A+B):
trait AuditModule+AuditCatalogRegistry mit Topo-Sort; alle 12 Module migriert; table-driven WCAG-Page-Rule-Catalog;audit/interpretation.rs(pre-computed DE/EN-Texte);audit/summary.rs(Aggregations-Logik); Builder ist reiner Mapper (#330–#338) -
Branch:
main -
Cache:
--reuse-cachevalidiertCacheMeta.audit_signature(Tool-Version + WCAG-Level + aktive Module + Consent) gegen die aktuelle Konfiguration; bei Mismatch Cache-Miss + Warnung, Legacy-Cache ohne Signatur wird nie wiederverwendet (#260) -
Crawler: parserbasierte Linkextraktion via html5ever inkl.
<base href>(#263) -
Batch-JSON: optionaler
sample-Block (source, total_discovered, audited, sample_limit, selection, is_sample) + PDF-Prüfumfang-Zeile (#261) -
Performance:
VitalMetric.measurement(lab_headless/estimated_lab); INP/TTI/Speed Index als Lab-Schätzung markiert, Lab-Disclaimer im Report (#262) -
Kontrast: Bild-/Gradient-Hintergründe werden zu Manual-Review-Warnungen demoted statt als bestätigte Verstöße (#264, Pixel-Sampling offen)
-
Accessibility Journey Layer (
--interactive off|basic|full): Tab-Walk, Skip-Link, Disclosure, Modal, TabList, Menu, Form-Error-Announcement, SPA-Navigation, Linktext-/Heading-/Landmark-Inventur (#297–#301). Ergebnisse ininteractive_findings+accessibility_journeyim JSON. -
Snapshot Export (
--export-snapshot <path>): AXTree + Journey-Traces als YAML für CI-Regression (#301). -
Linktext-Stopwords in i18n FTL (
locales/de|en/report.ftl, Schlüssellinktext-generic-stopwords) — erweiterbar ohne Code-Änderung (#299). -
95+ WCAG rules implemented (Level A, AA, full AAA coverage)
-
2 output formats (json, pdf); table for quick terminal checks
-
Batch processing with configurable concurrency
-
Pattern Detection: MainNavigation, SkipLink, Accordion, Dialog, DisclosureMenu, TabList, Form
-
Modules: Performance, SEO, Security, Mobile, Dark Mode, Design Quality (opt-in, score-neutral), UX, Journey, AI Visibility, Content Visibility, Source Quality, Tech Stack, Best Practices, Commerce, Accessibility Journey Layer
-
Consent:
--dismiss-consentFlag; CMP-Cookie-Injection + Banner-Click;consent_banneraudit_flag im JSON -
audit_flagskinds:conflicting_signal(3.1.1 vs. SEO lang),viewport_gap(Desktop/Mobile ≥20 Punkte),consent_banner,consent_wall_artifact,bypass_blocks_untested(Skip-Link vorhanden aber funktional kaputt — statischer Check hat PASS, Journey FAIL) -
JSON: Unified Report Envelope v2.0 — einheitliches Schema für single + batch (
schema_version,report_type,summary,pages[],pages[i].detail). Breaking Change ggü. v0.17. -
Scoring: Depth-Saturation (Zwei-Phasen), Diversity-Faktor, Soft Floor + logarithmische Kompression für extreme Penalties (≥85 Punkte), WCAG-Prinzip-Coverage;
score_breakdown(nur beiscore_calculation_method = "viewport_weighted", sonst absent) -
Findings:
category-Feld aufNormalizedFinding("wcag"/"seo");severity_countszählt Findings (eine Zeile pro Regel/Severity, nur WCAG-Kategorie — bleibt risiko-/rechts-relevant). Im JSON-Report deckenoccurrence_counts,violation_countundviolated_rule_countalle Kategorien (WCAG + SEO) ab — konsistent mitfindings[]unddetail.fix_guidance(#254/#255).top_recurring_rulesbleibt WCAG-only. Achtung:NormalizedReport.occurrence_countsist weiterhin WCAG-only (speistSiteState/Risk), der JSON-PageEntry berechnet die All-Category-Variante separat.risk.severity= schwerste Violation über alle Findings (kein eigenesseverity_max-Feld) -
Risk Level: Score-basierter Fallback (score ≤ 20 → mindestens Medium);
legal_flags > 0oderblocking_issues ≥ 1heben das Level mindestens auf Medium.legal_flagszählt distinct WCAG-Level-A-Regeln mit High/Critical-Severity (nicht Occurrences). -
History:
schema_version: "1.0",report_type: "history"in History-JSON-Dateien -
PDF: Throttled-Performance-Tabelle, Indikator-Kennzeichnung konsistent, leere Seite nach ToC behoben; Accessibility-Journey-Section in Single- und Batch-Reports
-
Performance-Score: Lighthouse-v10/v11-Gewichtung (FCP 10 %, LCP 25 %, TBT 30 %, CLS 25 %), log-normale Score-Kurven mit p10/p50-Kalibrierung; CLS > 0.5 hart auf 0 gecappt
-
tool_versionals Top-Level-Feld im JSON-Report (parallel zuschema_version/report_type) -
Sitemap-Summary enthält
violated_rule_count(dedupliziert über alle Pages) undtop_recurring_rules(max. 10 häufigste WCAG-Verstöße) -
Pass-Kriterium (
passed_url_count): accessibility_score ≥ 80, keine Critical-Findings und keine WCAG-Level-A High/Critical-Findings (alsolegal_flags == 0) -
detail.fix_guidanceist immer im JSON präsent (leeres Array bei 0 Findings) — auch in Batch-/Sitemap-Reports; dort trägt jede Page ein kompaktesdetail(nurfix_guidance, ohne Modul-Blob), siehe #256