Zdůvodnění
Tento repozitář servíruje člověku čtyři povrchy — web publikovaný na GitHub Pages, Cockpit, stránky, které si vykresluje spustitelný soubor sám, a shell Swaggeru. Verze 1 tohoto pravidla učinila z palety a sad písem jednu deklaraci a cestou našla sedm palet. Audit pro verzi 2 našel zbytek designu tam, kde byl vždy.
Barvy webu byly výchozí téma Flowbite, soubor třetí strany, a deklarace je pro Cockpit opakovala — dva vlastníci jednoho rozhodnutí. Stav měl tři palety: emerald/amber/rose v deklaraci, green-700/amber-700/red-700 v Cockpitu, emerald-900/orange-900/rose-900 na webu — a které slovo znamená který stav, bylo zapsáno dvakrát, jako seznamy selektorů v jedné stylesheet a jako řetězce if/elif ve čtyřech šablonových makrech. Tmavá paleta Cockpitu ležela pod :where(.dark), specificitou prohrála s :root a nikdy se neuplatnila. Cockpit měl vlastní favicon a písmenné logo vedle hranolu webu. Dva klíče motivu. Šest pixelových velikostí v hranatých závorkách. A nic nikde neumělo říct, k čemu token slouží.
Nic z toho nebyla chybná hodnota. Byl to návrh bez kanonického domova pro většinu svých rozhodnutí, takže je každý povrch dělal znovu a žádná kontrola nedokázala odlišit poslušnost od vymýšlení. ADR 0031 zaznamenává první opravu; ADR 0036 zaznamenává tuto.
Požadované chování
Rozhoduje jeden soubor. share/design/tokens.yaml drží identitu a značky, dvě sady písem, surovou paletu, sémantické role, významy stavů a slovník zařazený pod ně, pojmenovanou typografickou stupnici a prostrkání, sdílené hodnoty rozvržení, poloměry, délky trvání, kontrakt motivů, auditní šířky a slovník webu jako aliasy. Nic jiného o ničem z toho nerozhoduje; vše ostatní se odvozuje.
Crate to čte jako typovaný model. crate::design::DesignSystem parsuje deklaraci vlastní podmnožinou YAML vrstvy, zachovává pořadí deklarace, validuje ji a počítá její otisk. Odmítne roli, která nepojmenovává žádný prvek palety, stav odkazující ani na prvek palety, ani na roli, slovo stavu zařazené pod dvěma významy nebo pod názvem jiného stavu, alias ničeho, typografický krok bez jednotky nebo kladné výšky řádku, neklesající seznam šířek a jakoukoli hodnotu, kterou by CSS parser tiše zahodil — prázdnou hodnotu, nevyváženou uvozovku. Odmítnutá deklarace nic nepromítne.
majordomus generate design zapisuje každou projekci. Tailwind @theme, který importují oba buildy, nese sady písem, škálu, rozestupy písmen, poloměry a slovník webu jako var(--mj-<role>) po vlastním tématu Flowbite; povrchový list se všemi barvami rolí a stavů jako --mj-* v základní vrstvě, světlý na :root a tmavý pod třídou tématu jako selektor třídy po něm; stavový list s jednou skupinou selektorů na každé stavové slovo, nastavující tři nepřímé vlastnosti, které čtou primitiva; tytéž tokeny jako prosté vlastní proměnné zkompilované do spustitelného souboru; samotná deklarace zkompilovaná do spustitelného souboru; každá kopie značky, kterou kterýkoli povrch servíruje; dataset webu se slovníkem, smlouvou tématu, prohlášením před vykreslením a šířkami; a docs/generated/design.{json,yaml,md}. Každý nese banner původu a generate --check ho porovnává jako každý jiný generovaný artefakt.
Plocha pojmenovává tokeny, nevymýšlí je. Stylesheet, šablona, skript dodávající hodnoty CSS nebo Rust zapisující blok <style> první strany smí odkazovat na token a nesmí zapsat barevný literál, surovou paletovou utilitu Tailwindu (text-green-700), velikost v hranatých závorkách (text-[11px]), druhou deklaraci kanonické vlastní CSS vlastnosti, klíč motivu ani třídu motivu. Skript, který čte token ze stránky, se vrací k barvě, kterou stránka už vypočítala, nikdy k literálu.
Stav je jeden slovník. Slovo stavu se barví podle významu, pod který je zařazeno, a podle ničeho jiného. Každý povrch vykresluje stav jako mj-badge--<word>, mj-alert--<word> nebo mj-status--<word> a nechá generovaný stylesheet, aby ho obarvil; slovo, které deklarace nezařazuje, se vykreslí neutrálně a kontrola webu i sonda Cockpitu je odmítnou.
Sdílené primitivy, sdílený základ. Komponenty, které vykresluje více než jeden povrch, žijí v share/design/primitives.css nad tokeny; přístupný základ — viditelný focus ring u všeho, k čemu se dostane klávesnice, respektování omezeného pohybu — žije v share/design/base.css; oba vstupní body Tailwindu importují obojí. Povrch si ponechává jen to, co má sám.
Jeden kontrakt motivu, jedna značka. Třída, která znamená tmavý, a klíč úložiště pocházejí z deklarace; každý povrch dodává generovaný příkaz před vykreslením a jeho skripty čtou klíč i třídu ze stránky. Značky žijí pod share/design/brand/; každá kopie je generovaná a porovnávaná bajt po bajtu; žádná jiná značka neexistuje.
Nový token nevyžaduje registraci. Role, barva stavu, slovo stavu, typografický stupeň, hodnota rozvržení přidaná do deklarace a znovu vygenerovaná je v každém stylesheetu, v datové sadě webu, v inventáři, explain a na stránce Design v Cockpit bez úpravy jediného konzumenta. test/cases/109_design_system.sh a renderovací testy crate to dokazují tím, že to dělají.
Deklarovaná dvojice je čitelná. Popředí se vykresluje na pozadí a které pozadí to je, lze zjistit z deklarace a primitiv, které ji spotřebovávají — takže se měří, ne posuzuje. Každá dvojice v obou motivech dosahuje WCAG 2.1 AA (4,5:1 pro text). Dvojice pod hranicí je zjištění s uvedením role, pozadí, motivu a poměru; oprava je hodnota v share/design/tokens.yaml, nikdy lokální přepsání. Ohraničení nebo obrys se měří proti 3:1 (1.4.11) a hlásí se, nikoli odmítá: primitiv, který jej kreslí, vždy ukazuje vedle něj slovo, takže hranice, kterou toto pravidlo vynucuje, je ta textová.
Stránka to pozná. Každý vygenerovaný stylopis nese --mj-design, otisk deklarace; spustitelný soubor tentýž otisk vtiskne na každou stránku, kterou vykreslí, a odpovídá jím z design.system. Cockpit oba porovná při načtení; sonda webu a sonda Cockpitu ověřují, že stylopis, stránka a spustitelný soubor se shodují při každé deklarované šířce. Stylopis zkompilovaný před posunem deklarace nebo spustitelný soubor sestavený před jejím opětovným vygenerováním je vidět na stránce, a ne v diffu, který nikdo nespustil.
Výjimka je důvod. Generované projekce jsou z literálů, protože to je jejich úkol; bajty třetích stran a zkompilované artefakty se zde nepíší; kanonické značky pod share/design/brand/ nemohou dosáhnout vlastní CSS vlastnosti; model, který deklaraci implementuje — crate::design — parsuje, rozlišuje a vykresluje barvu, takže jeho unit fixtures jsou deklarace v miniatuře a jeho příklady říkají, na co se hodnota rozliší, a nikomu nic nevykresluje (poctivost zajišťuje, že každou hodnotu, kterou pro plochu vydá, porovnává generate --check bajt po bajtu); jeden skript počítá kategoriální odstín pro každý druh uzlu za běhu; tři měří či počítají barvu a nic nemalují. Každá je uvedena tam, kde je kontrola napsána. Cesta přidaná bez důvodu je vada, kterou toto pravidlo nahrazuje, jen v jiné čepici.
Chování při selhání
scripts/ci/design-check skončí s exit 10, když kterýkoli první-stranový zdroj nese vlastní designové rozhodnutí, když se kopie značky liší od kanonického souboru, nebo — v tomto repozitáři, je-li po ruce sestavený spustitelný soubor — když je projekce zastaralá; běží jako kontrola design a nepotřebuje prohlížeč ani node. majordomus generate --check odmítne zastaralou projekci v úloze rust a scripts/cockpit-assets --check odmítne zkompilovaný stylesheet, který se liší od svého zdroje, nebo položku palety, která se liší od nainstalovaného Tailwindu, v úloze site. scripts/site-check odmítne slovo odznaku, které deklarace nezařazuje, a stránku, která nenese otisk deklarace. scripts/site-probe a scripts/cockpit-probe odmítnou stránku, jejíž stylesheet nese jiný otisk než datová sada nebo spustitelný soubor, nebo která se vykresluje v jiném stacku než deklarovaném.
Toto pravidlo řídí, kde se hodnota deklaruje, a klade jednu spodní mez na samotnou hodnotu: dvojice barev, kterou nelze přečíst, je odmítnuta. To není rozpor. Zda má být akcent modrý, je záměr — stanoví ho člověk v jediném souboru, který ho drží — ale zda text v té barvě na tom pozadí dosahuje WCAG 2.1 AA, je měřitelné a deklarace není místo, kde se stanoví, že něco bude nečitelné. Vše nad mezí je vkus a toto pravidlo o tom mlčí.
Spodní mez není zadarmo a první měření ukázalo, proč ji nutno vynucovat, a ne jen předpokládat. Deklarace jmenovala tři úrovně tlumeného textu — muted, pod ním faint — a faint na stránce naměřilo 2,60:1. Oprava, která by zachovala tři úrovně, neexistuje: na bílé povoluje 4,5:1 relativní svítivost nejvýše 0,1833, což je percepční světlost asi 56,8 %, a muted už sedí na 55,1 %. Každý čitelný faint je pro oko muted. Třetí úroveň nikdy nebyla úrovní; byl to text, který nikdo nepřečetl, a hierarchii, kterou měla nést, nese místo ní velikost, tloušťka a prostrkání, které kontrast nestojí. Pravidlo, které by se ptalo jen, kde je hodnota deklarována, by ji nechalo navždy.
Ověření
test/cases/107_design_tokens.sh dokazuje kontrolu mutací: barevný literál, surová utilita palety, velikost v závorkách, druhá deklarace, čtení tokenu, který nic nevydává, ručně upravená kopie značky, ručně pojmenovaný klíč tématu — každé odmítnuto; tentýž soubor pojmenovávající token a každá zdokumentovaná výjimka prochází. test/cases/109_design_system.sh dokazuje projekci přes sestavený spustitelný soubor: stavové slovo a role přidané do deklarace fixture se dostanou do každé šablony stylů, do datové sady webu a do inventáře v jednom generate design; nekonzistentní deklarace neprojektuje nic; generate --check odmítne zastaralou projekci; a schopnosti odpovídají stejným otiskem, jaký nesou šablony stylů. Vlastní testy crate drží odmítnutí validátoru, tvar rendereru — včetně toho, že tmavý blok je třída za kořenovým blokem — a vlastnost nulové registrace. test/cases/120_design_contrast.sh dokazuje hranici čitelnosti stejným způsobem: deklarace se měří čistě v obou tématech; pravidlo přidané do primitiva se měří bez čehokoli registrovaného kdekoli, takže množina dvojic se odvozuje a nevypisuje; a pravidlo, které dává barvu na pozadí, na němž ji nelze přečíst, je nález, který jmenuje roli, pozadí, téma a poměr, a který scripts/ci/design-check odmítne. ADR 0036 zaznamenává rozhodnutí a co zůstalo nedokončeno.