42 files · 15,142 lines · 78 declarations
Was bringen logische Eigenschaften wirklich?
Zuerst dem Werkzeug geben, was ihm zusteht, und zwar in Zahlen. Die Stylesheets dieser Seite kommen auf 15.142 Zeilen in 42 Dateien, und 78 dieser Zeilen sind logische Deklarationen auf der Inline-Achse — padding-inline, margin-inline, inset-inline-start, text-align: start. Dagegen stehen elf ausdrückliche Blöcke mit Spiegelregeln, die auf einer rtl-Wurzel greifen, dazu neunzehn weitere, die auf die arabische Sprache greifen und von denen keine einzige eine Spiegelung ist. Die Kaskade erledigt das Richtungsproblem fast allein, und sie erledigt es in der Schicht, die den größten Teil einer Seite ausmacht.
Die Grenze ist dokumentiert, nicht entdeckt. MDNs eigene Referenz zum Modul listet logische Entsprechungen für Größen, Margins, Paddings, Borders, Insets, float und clear, resize, caption-side und text-align — und zählt dann die Features auf, die keine haben und keine bekommen werden: transform, transform-origin, background-position, box-shadow, text-shadow, die Winkel von linear-gradient, die Position von radial-gradient und filter. Die genannte Begründung ist bei allen dieselbe: sie arbeiten in einem gerenderten Koordinatenraum statt im Dokumentfluss. Logische Transforms sind seit dem 23. Oktober 2018 ein offenes Issue der CSS Working Group, w3c/fxtf-drafts Nummer 311, und sie sind es bis heute.
Die interessante Frage ist also nicht, was die Kaskade abdeckt. Sie lautet, was übrig bleibt, und auf dieser Seite waren das fünf Schichten, in denen logisch überhaupt kein Begriff ist, den die Plattform definiert: der Koordinatenraum, in dem ein Transform lebt, die Reihenfolge des DOM, wie der Bidi-Algorithmus einen Lauf auflöst, wie ein Text-Splitter ein Wort zerteilt und was die Metriken einer Schrift messen. Sieben Fehler aus dieser Codebasis erreichten arabische Leser, und jeder einzelne saß in einer dieser fünf.
| Feature | Logische Entsprechung | Blöcke mit Spiegelregeln |
|---|---|---|
| padding-inline, margin-inline, inset-inline, alle Formen | ja — 78 Deklarationen nutzen sie | 0 |
| translateX und translate3d | nein | 2 |
| scaleX | nein | 2 |
| transform-origin | nein | 1 |
| Winkel von linear-gradient | nein | 2 |
| Reihenfolge der Flex-Items eines zusammengesetzten Werts | nein — die Reihenfolge folgt der Leserichtung | 3 |
| sequentielle Fokusreihenfolge | nein — DOM-Reihenfolge, unbeeinflusst | 1 |
Auch die Unterstützung ist keine Einschränkung mehr. caniuse führt CSS Logical Properties and Values Level 1 mit 96,42 Prozent weltweit, mit voller Unterstützung ab Chrome 89, Edge 89, Firefox 66 und Safari 15. Die Lücke, die die Arabic Script Gap Analysis des W3C weiterhin verzeichnet, ist schmaler und genauer: logische Keywords in Kurzschreibweisen werden schlecht unterstützt, und deshalb zählen die Zahlen oben Langschreibweisen.
src/runtime/pages/projects.js:180-201
Warum ignoriert ein Transform, in welche Richtung die Seite gelesen wird?
Der Projekt-Index legt zehn Fallstudien auf eine waagerechte Leiste: eine Spur, die seitwärts fährt, während die Seite nach unten scrollt. Ihr Stylesheet ist vollständig logisch — die Spur steht auf inset: 0 mit padding-inline: 8vw —, also überläuft die Spur in einem arabischen Dokument korrekt nach links, und genau das soll sie tun. Am CSS war nichts falsch.
Das JavaScript hat sie dann noch weiter nach links geschoben. Gemessen auf der arabischen Projektroute, bis ans Ende der Leiste gescrollt, erreichte die Custom Property, die die Spur liest, −2.531 und schob eine Spur, die schon links außerhalb begann, noch weiter hinaus. Null der zehn Karten lagen im Viewport, bei 1440 wie bei 390, und der Zähler in der Ecke stand die ganze Strecke über auf 10. Ein arabischer Leser kam über die Leiste an keine einzige Fallstudie.
Die Ursache steckt in der Spezifikation, nicht im Browser. CSS Transforms Level 1 definiert den Koordinatenraum mit zwei Achsen: die X-Achse wächst waagerecht nach rechts, die Y-Achse wächst senkrecht nach unten. In dieser Definition kommt kein Begriff für die Leserichtung vor, also fährt ein translate3d auf beiden Dokumenten physisch denselben Weg, egal was direction sagt. Der Betrag ist im arabischen Dokument dieselbe Größe wie im englischen; nur das Vorzeichen ist eine Tatsache über die Leserichtung.
// src/runtime/core/env/direction.js — 64 Zeilen, null Imports
export function isRtl(doc) {
return (doc?.documentElement?.dir ?? '') === 'rtl'
}
export const MIRROR = Object.freeze({ right: 'left', left: 'right' })
export function inlineSign(doc) {
return isRtl(doc) ? 1 : -1
}
// src/runtime/pages/projects.js — der Scrub-Callback
// vorher: const x = (-progress * span).toFixed(1)
const sign = inlineSign(doc)
const x = (sign * progress * span).toFixed(1)
// tests/unit/motion-primitives.test.mjs
assert.equal(railTravel(4000, 1440, false), -2560)
assert.equal(railTravel(4000, 1440, true), 2560)
Derselbe Fehler ist zweimal in derselben Form ausgeliefert worden. Die Journey-Leiste der Startseite ist mit padding-inline: 75vw gepolstert, ihre Spur überlief also ebenfalls korrekt nach links, und ihr Tween hatte ebenfalls einen negativen Weg fest verdrahtet — und zog eine ohnehin nach links überlaufende Spur aus ihrem eigenen Clip, bis alle vier Kapitelkarten schlicht weg waren. Nach der Korrektur gemessen: die Spur driftet auf der arabischen Startseite +1.199 Pixel und auf der englischen −1.199, und ein Unit-Test sichert zu, dass der Betrag identisch ist und sich nur das Vorzeichen unterscheidet.
Hier reicht auch ein CSS-Flipping-Build-Schritt nicht mehr hin. RTLCSS spiegelt richtungsabhängige Deklarationen, Transforms eingeschlossen, und seine eigene Dokumentation sagt offen, dass sich manche Werte ohne eine Direktive des Autors nicht spiegeln lassen. Es dreht die gebaute Fassung einer CSS-Datei — und die Zahl, die diese Leiste kaputt gemacht hat, gab es zur Build-Zeit gar nicht. Sie wurde pro Frame berechnet und in eine Custom Property geschrieben, also an eine Stelle, die keine Stylesheet-Transformation sehen kann. Das Touch-Dragging, das diese Woche zur selben Leiste dazugekommen ist, nimmt seine Richtung aus genau diesem Helfer, sodass ein Finger, der nach rechts fährt, die arabische Sammlung vorwärts bewegt statt rückwärts.
Die Leiste, auf der das passiert ist, ist immer noch der Weg zu den zehn Builds — der Projekt-Index, jetzt in drei Sprachen erreichbar.
legacy-forensics/a10-css.md §AA
Warum liest sich ein Zähler rückwärts, wenn nichts an ihm arabisch ist?
Das klarste Lehrbeispiel dafür wurde auf dem ALTEN abbod.de gemessen — der Seite vor dem Rebuild, dem Golden Master, gegen den dieses Projekt verglichen wird — und es ist eine Liste ganz gewöhnlicher lateinischer Werte, die auf den arabischen Seiten rückwärts rendern. Ein Kartenindex, ein Hex-Farbwert, ein Social-Handle, eine Größenangabe und die Wortmarke des Loaders selbst. Keiner davon enthält ein einziges arabisches Zeichen.
Die Forensik-Datei, die sie festgehalten hat, hatte die Familie schon in die zwei Mechanismen aufgeteilt, die sie braucht, und die Unterscheidung ist der nützliche Teil. Sitzt der ganze Wert in einem Textknoten, löst ihn der Unicode-Bidi-Algorithmus gegen den umgebenden Absatz auf, und die Korrektur ist eine Bidi-Deklaration am Element. Ist der Wert über mehrere Inline-Boxen verteilt — ein Trenner-Span, ein Wrapper pro Buchstabe —, hilft der Algorithmus überhaupt nicht, denn jede Box ist ihr eigener Lauf und es bleibt nichts mehr übrig, was er umsortieren könnte. Dieser Fall braucht die gesetzte Basisrichtung: direction: ltr.
UAX Nummer 9, Revision 51, sagt das in seinem eigenen Geltungsbereich. Er löst eine Absatzebene auf, explizite Einbettungsebenen, schwache Typen, Neutrale und implizite Ebenen, und all das innerhalb jedes Absatzes von Zeichen. Wie Inline-Boxen oder Elemente angeordnet werden, liegt außerhalb davon. Diese Anordnung ist Sache der Flexbox-Spezifikation, und Abschnitt 5.1 ist eindeutig: die Hauptachse eines Row-Flex-Containers hat dieselbe Ausrichtung wie die Inline-Achse des aktuellen Schreibmodus. Drei Flex-Items, die 01, einen Schrägstrich und 04 lesen, werden in einem arabischen Dokument also umgekehrt angeordnet, und jede Karte im Journey-Abschnitt dieser Seite behauptete, die vierte von einer zu sein.
| Geschrieben | Auf der alten arabischen Seite gerendert | Was es brauchte |
|---|---|---|
| 01 / 04 | 04 / 01 | direction: ltr — über Boxen verteilt |
| LoadAbbod | dobbAdaoL | direction: ltr — eine Box pro Buchstabe |
| #C4552D | C4552D# | unicode-bidi — ein Textknoten |
| @alaaabbod | alaaabbod@ | unicode-bidi — ein Textknoten |
| 6 KB JS | KB JS 6 | unicode-bidi — ein Textknoten |
Zwei weitere wurden festgehalten und lassen sich in einem Artikel schlecht abdrucken: eine Telefonnummer, die sich zu Unsinn umdrehte, und ein Markup-Beispiel, dessen schließendes Tag seine spitzen Klammern tauschte, sodass der gezeigte Code nicht geparst hätte. Beide sind der Fall mit einem Textknoten, und beide sind der Grund, warum diese Seite heute auf jeder arabischen Route zusichert, dass alles in einem code- oder pre-Element zu direction: ltr berechnet wird.
Der Zähler dreht sich wegen der Spezifikation um und nicht wegen eines Browser-Fehlers, was leicht gesagt und die Reproduktion wert ist. In isoliertem Headless-Chromium unter einer rtl-Wurzel rendert ein Inline-Flex-Zähler aus 01, einem Trenner-Span und 10 sichtbar als 10, dann der Schrägstrich, dann 01, mit Fragment-Rechtecken bei left 748, 768 und 776. Dasselbe Markup mit direction: ltr und unicode-bidi: isolate rendert in der Reihenfolge, in der es geschrieben wurde, an denselben drei Positionen.
23 sources · 0 chars on /ar/ · 201 on /
Warum schreibt das Zerlegen von Text arabische Wörter falsch?
Ein Zeichen-Roll braucht jedes Zeichen als Box, die er bewegen kann. CSS Transforms Level 1 definiert ein transformierbares Element als alles, was dem CSS-Box-Modell folgt, außer nicht ersetzten Inline-Boxen, Tabellenspalten-Boxen und Tabellenspaltengruppen-Boxen — ein nackter Inline-Span pro Buchstabe ist also keines. Hier gemessen als ein Tween, der −70.00 Pixel hätte zurücklegen sollen und 0.00 zurücklegte. Jedes erzeugte Zeichen auf inline-block zu setzen ist keine Stilfrage; ohne das tut der Effekt gar nichts.
Eine atomare Inline-Box pro Buchstabe ist eine Box, über die der Shaper nicht hinweg verbinden kann, und eine verbundene Schrift ist eine, in der die Form eines Buchstabens von seinen Nachbarn abhängt. Jedes arabische Wort auf dieser Seite, das das Split-Attribut trug, renderte als Reihe isolierter Formen — 23 Quellen, darunter alle vier Menülinks und jeder Footer-Link. Das Wort für Kontakt aufnehmen kam als fünf einzelne Buchstaben heraus, die nichts ergeben. Das ist keine feine Verschlechterung: es ist ein Rechtschreibfehler, auf jedem Navigations-Label, das die Seite hat.
Es gibt keine Lösung im Styling, und genau darum geht es in diesem Abschnitt. Setzt man die Zeichen zurück auf inline, kehren die Verbindungen zurück und das Transform ist tot; lässt man sie inline-block, bleibt das Transform und die Verbindungen sind tot. Die beiden Anforderungen schließen einander per Spezifikation aus, und CSS Text 3, Abschnitt 7.3, listet die Bedingungen auf, unter denen das Shaping an einer Inline-Box-Grenze brechen muss — ein Inline-Margin, -Border oder -Padding ungleich null, ein vertical-align abseits des Initialwerts, eine Bidi-Isolationsgrenze. GreenSock sagt dasselbe über sein eigenes Werkzeug in einfacheren Worten: SplitText wurde nicht für Sprachen von rechts nach links entworfen, und eine Korrektur ist nicht geplant.
- Der Effekt verlangt eine Box pro Zeichen Ein Roll pro Buchstabe ist ein Transform, und ein Transform gilt nicht für ein nicht ersetztes Inline-Element. Die erzeugten Zeichen müssen inline-block sein, damit die Animation überhaupt etwas bewegt.
- Der Splitter tut, worum er gebeten wurde, auf Graphem-Ebene SplitText 3.13 segmentiert mit Intl.Segmenter auf Graphem-Ebene und legt jedes Graphem in ein eigenes Span. Für die Einheit, nach der gefragt wurde, ist das korrektes Verhalten.
- Der Shaper hält an der Grenze an Jedes dieser Spans ist jetzt eine atomare Inline-Box, und die Verbindungen, die ein arabisches Wort zu einem Wort machen, kommen nicht darüber hinweg. Die Buchstaben fallen auseinander, in der Reihenfolge, in der sie geschrieben wurden, und ergeben nichts.
- Der Bidi-Algorithmus kann es nicht retten Er ordnet Zeichen innerhalb eines Absatzes, nicht Boxen innerhalb einer Zeile. Bei einem lateinischen Wort, das genauso zerlegt wird, ist das Ergebnis das Spiegelbild desselben Problems: die Buchstaben drehen sich um, weil jeder sein eigener Lauf ist.
- Also ändert der Split seine Einheit Aus einer Anfrage nach Wörtern und Zeichen wird eine Anfrage nach Wörtern; aus Zeilen, Wörtern und Zeichen werden Zeilen und Wörter; und eine Quelle, die nur nach Zeichen gefragt hat, fällt auf Wörter zurück, statt in nichts zu zerfallen.
- Und er macht es an der Sprache fest, nicht an der Leserichtung Hebräisch läuft von rechts nach links und verbindet nicht, eine Prüfung auf die Leserichtung würde Hebräisch also aus einem Grund kaputt machen, der auf es nicht zutrifft. Die Prüfung liest das Sprach-Subtag und ist mit einem hebräischen Dokument in einer rtl-Wurzel unit-getestet, das false zurückgibt.
Das Ergebnis wird in beide Richtungen zugesichert statt nur in eine, und das macht mehr aus, als es klingt. Das Gate prüft, dass das arabische Dokument null Zeichenknoten hat und das englische 201 davon, und getrennt davon, dass das arabische Dokument weiterhin Wörter und Zeilen zum Animieren hat — denn eine Prüfung, die nur null Zeichen zusichert, ginge auf einer Seite, auf der gar nichts zerlegt wurde, genauso zufrieden durch.
Aus derselben Korrektur fiel ein zweiter Fehler heraus, und er war aus einem Grund unsichtbar, der festgehalten gehört. Der Weg des Rolls war als zwei Literale geschrieben, die für die lateinischen Labels zufällig stimmen: dort steht line-height 1 bei einem Schatten-Offset in Höhe der Schriftgröße, minus hundert Prozent einer Zeichenbox und ein em sind also dasselbe Pixel. Auf den arabischen Labels, die auf 1.55 stehen, sind sie nicht mehr dasselbe Pixel — das eingehende Wort landete 0.45 em unter dem ausgehenden, und das ist der Versatz zur Grundlinie, den der Eigentümer für jeden arabischen Link auf der Seite gemeldet hatte. Die Korrektur liest den aufgelösten Offset des Zwillings, statt ihn anzunehmen.
Die Split-Hälfte davon hat ihren eigenen Text, samt der Frage, was ein arabischer Leser als die Einheit wahrnimmt, die zu animieren sich lohnt — Text in einer verbundenen Schrift zerlegen.
derived from Zain hhea · size-adjust 112%
Wie macht ein Clip-Fenster aus einem arabischen Wort ein anderes?
Der Label-Roll auf dieser Seite besteht aus drei Deklarationen an drei Stellen: einer line-height im Locale-Stylesheet, einem clip-path im selben Stylesheet und einem text-shadow-Offset in der Button-Komponente. Zusammen sind sie eine Zahl. Das Clip-Fenster ist der sichtbare Schlitz; der Schatten ist die Zwillingskopie des Wortes, die darunter wartet; die Zeilenbox entscheidet, wo beide sitzen. Ändere eine ohne die anderen zwei, und der Effekt bricht auf eine Weise, die nach einem Font-Problem aussieht.
Mit der lateinischen Geometrie auf einem arabischen Label — line-height 1, ein Fenster, das bei 94 Prozent der Box schneidet, ein Zwilling ein em tiefer — landet der Schnitt 0.153 em unter der Grundlinie. Ein finales Yeh in der ausgelieferten Schrift reicht 0.5096 em nach unten. Also werden 0.357 em Farbe entfernt, die beiden Punkte unter dem Buchstaben gehen mit, und das Wort rendert als ein völlig anderer Buchstabe. Der Golden Master hat genau das an seinem eigenen Footer-Link gemessen: das eigene Wort des Seiteninhabers für schreib mir, gedruckt als Fehlschreibung seiner selbst.
Die Zeilenbox allein zu öffnen behebt es nicht und bringt einen zweiten Fehler mit. Bei 1.55 und einem Fenster, das weiter bei 94 Prozent steht, sitzt der Schnitt 0.395 em unter der Grundlinie und das Yeh wird immer noch um 0.115 em beschnitten — und die Farbe des Zwillings beginnt jetzt 0.845 em von oben, in einem Fenster, das bis 1.457 em reicht, also erscheint unter jedem Label eine Geisterzeile des nächsten Wortes. Alle drei Zahlen müssen sich zusammen bewegen, und die Reihenfolge ist die Invariante: Zwilling unter Fenster, Fenster unter Farbe.
- 0.153 em lateinischer Schnitt, unter der Grundlinie line-height 1, Fenster 94 Prozent, Zwilling 1em
- 0.5096 em Unterlänge eines finalen ي die tiefste Farbe in der ausgelieferten Schrift
- 0.357 em vom Fenster entfernte Farbe راسلني gedruckt als راسلنى
- 0.705 em Schnitt im Auslieferungsstand, unter der Grundlinie line-height 1.55, Fenster 114 Prozent, Zwilling 2em
Im Auslieferungsstand bleibt das Yeh um 0.196 em frei, die Fensterunterkante liegt bei 1.767 em und die Farbe des Zwillings beginnt bei 1.845 em, ist also um 0.078 em verdeckt. Achtundsiebzig Tausendstel eines em sind der ganze Abstand zwischen einem funktionierenden Effekt und einer sichtbaren Geisterzeile, und das ist das Argument dafür, die drei Zahlen abzuleiten statt an ihnen zu schieben.
Die letzte Wendung hier betrifft den Geltungsbereich, nicht die Geometrie. Die Korrektur war für das arabische Dokument geschrieben, und der Fehler lebte auf dem englischen und dem deutschen weiter — an dem einen arabischen Wort, das diese Dokumente enthalten, dem Label des Sprachumschalters, das auf jeder Seite steht, damit ein Leser, der die aktuelle nicht lesen kann, seine eigene trotzdem erkennt. Dieses Wort erbte eine lateinische Familie ohne Arabisch darin, die Laufweite des Fließtexts, eine Zeilenbox von einem em und ein Fenster bei 94 Prozent. Ein lateinisches Dokument ist kein Dokument ohne Arabisch darin, und die Regel macht sich jetzt an der Sprache des Laufs fest statt an der Richtung der Seite.
Die line-height, die dieses Fenster öffnet, ist selbst eine Messung und kein Verhältnis — die echte Untergrenze berechnen.
Und der size-adjust, der das alles skaliert, kommt aus einem Zahn und einem Alif, weil die x-Höhe hier nichts bedeutet: eine arabische Schrift an einer lateinischen bemessen.
src/styles/components/site-nav.css:68-108
Warum behält der Header auf der arabischen Seite seine lateinische Anordnung?
Niemand hat eine Rechts-nach-links-Regel für den Header geschrieben. Er hat sich trotzdem gespiegelt, aus einem Richtungsattribut am Wurzelelement und einem justify-content: space-between, und er hat sich durch sieben verschachtelte Container weiter gespiegelt, die nie danach gefragt worden waren. Der Fehler hier ist dadurch entstanden, dass kein CSS geschrieben wurde, und das ist die Fehlerart, auf die niemand prüft.
Was dieser Flip gekostet hat, war nicht visuell. Die sequentielle Fokusreihenfolge ist die DOM-Reihenfolge und folgt der Leserichtung überhaupt nicht, also tabbte ein Besucher mit Tastatur auf den arabischen Seiten von links nach rechts durch eine Leiste, die von rechts nach links gemalt war. WCAG 2.4.3 verlangt, dass die Fokusreihenfolge Bedeutung und Bedienbarkeit erhält, nicht dass sie der visuellen Reihenfolge entspricht — für sich genommen ist das also kein Konformitätsverstoß. Der Grund, warum diese Seite die Leiste festhält, ist eine Entscheidung des Eigentümers, dass die Leiste ein Markenobjekt ist; dass DOM-Reihenfolge und visuelle Reihenfolge wieder übereinstimmen, ist ein echter Nutzen, der aus dieser Entscheidung folgt, aber nicht ihre Begründung.
Die Korrektur schaltet die Ursache ab, nicht die Effekte. Ein direction: ltr auf der Leiste und ihrem Menü-Wrapper schlägt sieben row-reverse-Deklarationen, und der achte Container, den nächstes Jahr jemand hinzufügt, ist standardmäßig richtig statt durch Auslassung falsch. direction ist die eine Eigenschaft, die jeder implizite Flip darunter liest: die Hauptachse von Flex, text-align: start, jede logische Eigenschaft und die Reihenfolge der Inline-Boxen. Danach werden die arabischen Läufe darin einzeln wiederhergestellt, mit unicode-bidi: isolate, damit ein Lauf seine Nachbarn in der linksläufigen Box, die ihn jetzt enthält, nicht umsortieren kann.
/* src/styles/components/site-nav.css */
/* eine Ursache, nicht sieben Effekte */
html[dir='rtl'] .nav,
html[dir='rtl'] .nav-menu-w {
direction: ltr;
}
/* und das Arabische darin bleibt Arabisch */
html[dir='rtl'] :is(.nav, .nav-menu-w)
:is(.text-nav-link, .text-eyebrow, .nav-lang-label, .btn-text.is-lang-ar) {
direction: rtl;
unicode-bidi: isolate;
}
Dasselbe Mittel hält das Diptychon aus Code und Design auf der Startseite fest, aus einem anderen Grund. Seine Hintergrund-Porträts sind physisch positioniert und wurden nie gespiegelt, also saß unter dem impliziten Flip das Argument jeder Spalte vor dem falschen Gesicht — und der Alternativtext sagte das laut. Das Layout festzuhalten bringt sie zurück; die Motion-Schicht brauchte an sechs Elementen ein passendes Opt-out-Attribut, damit die beiden Zeilen-Wipes über die Mittellücke hinweg einander zugewandt bleiben.
Eins solltest du wissen, bevor du für solche Regeln zu :dir() greifst. Es ist seit Dezember 2023 breit verfügbar und liest ausschließlich die semantische Richtung, die im Dokument deklariert ist. Die CSS-Eigenschaft direction berücksichtigt es nicht, ein Container, der gerade mit direction: ltr ausgestiegen ist, ist für :dir() also weiterhin ein rechtsläufiges Element. Attributselektoren am Wurzelelement leisten hier echte Arbeit, die die Pseudoklasse nicht leisten kann.
Die Laufweite gehört in denselben Abschnitt, weil sie dieselbe Art von Entscheidung ist und weil sie normativ ist statt stilistisch. CSS Text 3, Abschnitt 7.2.1, sagt, dass ein User Agent, der kursiven Text nicht dehnen kann, ohne seine Verbindungen zu brechen, zwischen keinem Buchstabenpaar dieser Schrift Abstand einfügen darf, und die Illustration der Spezifikation bezeichnet gleichmäßig verteilte Laufweite auf Arabisch als schlecht, mit der Anmerkung, dass sie die kursiven Verbindungen bricht. Der Grund, warum eine Seite trotzdem eine ausdrückliche Überschreibung braucht, ist, dass die Laufweite an den Elementen deklariert wird — Eyebrows, Buttons, Nav-Links, Display-Typo — statt vererbt zu werden, und genau das macht dies zu dem einen pauschalen !important, das das Projekt für gerechtfertigt hält.
noise-floor.json · measured 2026-08-19
Warum kann ein Screenshot davon nichts überwachen?
Das visuelle Haupt-Gate dieses Projekts ist ein Pixelvergleich gegen den Golden Master, und es hat überhaupt keine Locale-Achse. Das Viewport-Manifest deklariert keine, die Szenario-Bibliothek hat keine Locale-Logik, und der Capture-Schritt liest ein Locale-Feld aus einem Szenario, das kein Szenario setzt. Die Gate-Dokumentation sagt das selbst, in einer Tabelle unter der Überschrift, was dies nicht beweisen kann: Layout über Locales hinweg, Rechts-nach-links eingeschlossen, steht dort als nicht abgedeckt, mit der Anmerkung, dass die Richtung des Wurzelelements exakt verglichen wird, bisher aber nur als ltr beobachtet wurde.
Wo es läuft, ist es nahezu perfekt, und das ist die Falle. Gemessen an 19 Checkpoints am 2026-08-19 lag das strukturelle Pixel-Differenzverhältnis auf jedem Perzentil bei null, und 18 der 19 Checkpoints waren über zwei Läufe derselben Referenz bytegleich; die eine Ausnahme ist eine Laufschrift, die absichtlich live aufgenommen wird. Die Schwelle ist aus dieser Messung auf 0.0001 gesetzt, weil eine Schwelle danach gesetzt wird, was das Instrument beweisen kann. Dann wechselte die arabische Schrift auf Anweisung des Eigentümers von zwei Familien auf eine, und jedes arabische Pixel auf der Seite unterschied sich konstruktionsbedingt. Ein so exaktes Instrument baut nicht sanft ab. Es geht in einem Commit vom Beweis zu einer Wand aus Rot.
Der Accessibility-Scanner kann die Lücke auch nicht schließen, und es lohnt sich, genau zu sagen warum, denn er ist der Ersatz, den die meisten Teams annehmen. axe-core 4.11.0 bringt 104 Regeln mit. Filtert man alle 104 nach Regelbezeichner, Beschreibung und Hilfetext auf Richtung, Spiegelung, Bidi oder Layout-Reihenfolge, bleiben sechs, von denen vier Vorhandensein und Syntax des lang-Attributs prüfen und zwei Fehltreffer auf dem Wort structured sind. Diese Seite lässt ihn über jede Route bei zwei Viewports laufen — 144 Scans am 2026-08-24, fünf Verstöße —, und dieser Durchlauf hat mit allen sieben hier genannten Fehlern koexistiert.
- 18 von 19 bytegleiche Checkpoints zwei Läufe derselben Referenz, 2026-08-19
- 0.0001 die Schwelle für die Pixeldifferenz gesetzt danach, was das Instrument beweisen kann
- 104 Regeln in axe-core 4.11.0 144 Scans über die Seite am 2026-08-24
- 0 davon zur Leserichtung keine zu Spiegelung, Bidi oder Layout-Reihenfolge
Der Präzedenzfall dafür, was dagegen zu tun ist, lag schon im Repository. Die Canvas-Vergleichsachse ist als blockiert und nicht erzwungen markiert, weil ihr gemessener Rauschboden riesig ist — ein Differenzverhältnis von 0.025829 im 95. Perzentil über 89 Stichproben —, und die festgehaltene Begründung lautet, dass ein Gate, das eine Regression nicht von Rauschen unterscheiden kann, schlechter ist als gar kein Gate, weil es den Leuten beibringt, es zu ignorieren. Ein Vergleich, dessen Baseline absichtlich entwertet wurde, ist derselbe Fehlschlag mit einer anderen Ursache, und deshalb war die Antwort hier ein neues Instrument statt einer gelockerten Schwelle oder einer dauerhaften Ausnahmezeile.
Wie dieser Rauschboden gemessen wurde und warum eine Achse erzwungen wird und eine andere nicht, steht in dem Text über den Rauschboden.
Der Accessibility-Durchlauf hat seine eigene Geschichte über Bedingungen — aus 707 Kontrastknoten wurden fünf, und fünf zu veröffentlichen ist der Punkt — in dem Text über Kontrast-Fehlalarme.
37 checks · 4 viewports · 19 Arabic routes
Was eine Zusicherung über eine Eigenschaft zusichert
Was den Screenshot ersetzt hat, ist ein Satz von Eigenschaften, eine pro Fehler. Das Gate stellt 37 Zusicherungen auf: acht pro Viewport bei 390, 991, 992 und 1440 Pixeln Breite, dazu fünf gepaarte Prüfungen, die das arabische und das englische Dokument gegeneinander laufen lassen. Jede der acht war ein ausgelieferter Fehler, bevor sie eine Prüfung war, und das ist das einzige Aufnahmekriterium, das die Datei kennt. Jede hier zitierte Zahl ist aus dieser Quelle gelesen und nicht aus einem Lauf — das Gate braucht zwei lokale Server, und für diesen Artikel wurde keiner gestartet.
Seine Routenliste wird gelesen statt geschrieben. Die arabischen Routen kommen aus dem veröffentlichten Routen-Manifest, gefiltert auf die Locale, und die Datei wirft einen Fehler, wenn dieser Filter nichts zurückgibt. Diese Entscheidung hat diese Woche eine Dividende gezahlt: das Manifest ging von 72 Dokumenten und 20 arabischen Routen auf 69 und 19 zurück, als eine Projektseite abgeschaltet wurde, und das Gate hat die Löschung ohne eine Änderung aufgefangen, weil die acht Prüfungen pro Viewport über die Routen aggregieren, die das Manifest nennt.
Die mit Abstand wertvollste der 37 ist eine Subtraktion. Auf jeder arabischen Route nimmt sie die scrollWidth des Wurzelelements, zieht die innerWidth des Fensters ab und toleriert ein Pixel. Diese eine Zeile fängt jeden nicht gespiegelten physischen Versatz, jede hart gedeckelte Box, die Arabisch überläuft, und eine Leiste, die in die falsche Richtung fährt, alles auf einmal — und sie macht aus prüfe die ganze Seite auf Arabisch eine Messung statt einer Leseaufgabe.
Der Rest der acht ist absichtlich langweilig, und die langweiligen sind die, die eine Build-Regression fangen würden. Jede arabische Route muss sich am Wurzelelement als rechtsläufig und arabisch deklarieren. Nichts darf ohne beschneidenden Vorfahren über eine Inline-Kante hinausragen. Jedes code- oder pre-Element muss zu direction: ltr berechnen, denn ein Snippet, das die Richtung des Absatzes erbt, sortiert seine eigene Interpunktion um und ist kein gültiges Markup mehr. Und über alle 19 Routen darf es null Konsolenfehler, null fehlgeschlagene Requests und keine Antwort ab 400 aufwärts geben.
Eine Prüfung nennt ihre eigene Grenze, und das ist die Gewohnheit, die zu kopieren sich lohnt. Sie läuft jeden Textknoten im Body ab und sichert zu, dass jeder arabische Lauf zuerst nach der arabischen Familie fragt und dass jeder lateinische Lauf irgendwo in seinem Stack einen lateinischen oder monospacen Fallback behält. Mehr kann sie nicht zusichern: ein rein lateinischer Lauf in einem arabischen Dokument darf legitim zuerst nach der arabischen Familie fragen, weil ihn ein unicode-range-Deskriptor weiterleitet — und ein berechneter Stil kann nicht sehen, ob diese Weiterleitung gegriffen hat. Was die Prüfung beweisen kann, ist, dass kein Lauf nach einer Familie fragt, hinter der nichts mehr steht.
Diese Weiterleitung ist eine Geschichte für sich, denn die Familiennamen versprachen etwas, das die Font-Dateien nie geliefert haben — wenn eine arabische Familie dein Latein kapert.
src/styles/sections/projects-index.css:841-853
Eine Prüfung sichert nur die Eigenschaften zu, an die jemand gedacht hat
Die Prüfung auf umgekehrte Läufe verallgemeinert richtig. Ihre erklärte Regel lautet, dass ein Element, das ein zusammengesetzter Wert ist, seine Teile in der Reihenfolge anordnen muss, in der sie geschrieben wurden, und ihr Unterscheidungsmerkmal für zusammengesetzt ist der Leerraum: ein Kartenindex und eine Gesamtzahl sind ein Wert, dessen Teile zufällig drei Elemente sind, während ein Projektname neben seiner Nummer eine Layout-Zeile ist, die sich spiegeln soll. Diese Unterscheidung wurde auf dem ehrlichen Weg gelernt — der erste Lauf meldete auf einer Route sechs Fehlalarme und hätte dafür argumentiert, eine korrekte Zeile zu entspiegeln.
Die Implementierung sammelt die Kinder eines Elements ein, verwirft die aus dem Fluss genommenen und überspringt alles, wo weniger als zwei übrig bleiben. Der Zähler auf den Karten der Projektleiste ist ein Inline-Flex aus dem Text 01, einem Trenner-Span und dem Text 10 — drei Flex-Items, von denen genau eines ein Element ist. Die Prüfung läuft daran vorbei. Der Elternknoten wird ebenfalls übersprungen, durch die Leerraum-Regel, weil die Chips daneben Leerzeichen enthalten. Der Fehler also, aus dem die Prüfung geschrieben wurde, steht immer noch auf der Seite, auf der sie geschrieben wurde, zehn Karten weiter, und das Phasen-Handoff, das beide Zähler als behoben ausweist, irrt sich beim zweiten.
Genauigkeit über den Status, denn dies ist ein Befund und kein Bericht. Das Fehlen ist statisch und prüfbar: dieses Stylesheet enthält überhaupt keine direction-Deklaration, im letzten Commit wie im Arbeitsverzeichnis. Der Umkehrmechanismus ist in isoliertem Headless-Chromium exakt reproduziert. Auf der laufenden arabischen Projektseite ist er in dieser Sitzung nicht beobachtet worden — ein Gate-Lauf würde ihn dort bestätigen. Dieselbe Datei benutzt außerdem ein physisches margin-left: auto, wo der korrigierte Zähler margin-inline-start: auto benutzt, und in einer 400 Pixel breiten rechtsläufigen Zeile von 398 bis 800 landet die Pille durch diesen Unterschied bei 724 statt bei 399, gegen die Chips gequetscht statt am anderen Ende.
- Die Verallgemeinerung stimmte
- Ein zusammengesetzter Wert, angeordnet in der Reihenfolge, in der er geschrieben wurde. Das deckt einen Zähler, einen Hex-Farbwert, eine Telefonnummer und ein Code-Snippet in einem Satz ab.
- Die Implementierung war enger
- Sie vergleicht Element-Kinder, weil die beiden Fälle, aus denen sie geschrieben wurde — ein Code-Snippet und der Journey-Zähler —, beide welche hatten.
- Das Markup war anonym
- Zwei der drei Flex-Items des Projektzählers sind nackter Text ohne Element darum, also erzeugt der Browser anonyme Items, die das DOM nicht aufzählen kann.
- Der Elternknoten war mit Absicht ausgeschlossen
- Das Leerraum-Merkmal, das die Prüfung davon abhält, echte Layout-Zeilen zu entspiegeln, hält sie auch davon ab, in diese hier hinunterzureichen.
Die praktische Form von all dem ist kurz. Nutze logische Eigenschaften überall dort, wo sie hinreichen, und schreib dann eine Liste dessen auf, wohin sie in deiner Codebasis nicht reichen — Transforms, Gradientenwinkel, transform-origin, Schatten, die Reihenfolge eines zusammengesetzten Werts, die Fokusreihenfolge, das Shaping, die Font-Metriken — und geh diese Liste einmal von Hand auf den arabischen Seiten durch. Verwandle jeden Fehler, den du findest, in eine Zusicherung über eine Eigenschaft statt über Pixel. Und behandle Überspiegelung ebenfalls als Fehler: das Projekt-Deck dieser Seite lässt eine z-index-Formel bewusst ungespiegelt, weil ihre Eingabe schon logisch ist, und sie ein zweites Mal zu spiegeln hat neun der zehn Badges versteckt.
Wenn du einen Build in mehr als einer Schrift planst und die Richtungsentscheidungen lieber vor dem Layout klärst als danach, ist das ein kurzes Gespräch, das sich früh lohnt.