36. Design systém je typovaná deklarace v crate a každý first-party povrch spotřebovává její projekce
Kontext
ADR 0031 učinilo z palety a typografických sad jednu deklaraci, share/design/tokens.yaml, kterou shell skript promítá do tématu Tailwindu, bloku povrchu a zkompilované sady tokenů. Tím se uzavřela největší díra — sedm palet — a zbytek designu zůstal tam, kde vždycky byl, což audit proti mandátu dát webu a Cockpitu jeden vizuální jazyk jasně ukázal:
- Barvy webu nebyly barvami deklarace. Web je napsán ve slovníku Flowbite —
text-heading,border-default,bg-brand,text-fg-success-strong— a tato jména se rozlišovala na výchozí téma Flowbite, soubor třetí strany. Deklarace opakovala hodnoty, na které se Flowbite rozlišovalo, pro Cockpit, a světlé téma nazývala „co web už vykresluje“. To jsou dva vlastníci jednoho rozhodnutí, z nichž jeden je vnode_modules. Kde se rozcházeli, už se rozešli: tmavé téma webu bylo rodinou gray-950 z Flowbite a to v Cockpitu ručně vybraným odstínem 250; text těla webu byl gray-600 a v Cockpitu gray-900; varování webu bylo oranžové a v Cockpitu jantarové. - Stav byl tři palety. Deklarace říkala, že
ok/warn/badjsou smaragdová, jantarová a růžová; stylopis Cockpitu barvilsucceededTailwindovoutext-green-700,cancelledpomocítext-amber-700afailedpomocítext-red-700; web barvilDONEFlowbitovou emerald-900,VERIFYorange-900 aBLOCKEDrose-900. Která slova znamenala který stav, bylo zapsáno dvakrát — jako seznamy selektorů v jednom stylopisu a jako řetězceif/elifve čtyřech makrech šablon — a nové slovo se muselo přidat do obou, jinak ho obarvil ten povrch, na který si kdo vzpomněl. - Tmavá paleta Cockpitu se nikdy neuplatnila. Její tmavé hodnoty seděly pod
:where(.dark), které nemá žádnou specificitu, a její světlé pod:root, které nějakou má; obojí odpovídá<html class="dark">, takže na každém prvku vyhrála světlá. Přepínač motivu přehodil třídu, utility s variantamidark:ji následovaly a stránka zůstala bílá. Žádná kontrola neměla, jak si toho všimnout. - Dvě značky. Značka webu byl hranol nakreslený v
currentColor, uložený podassets/a kopírovaný buildem webu; favicon Cockpitu byl modrý čtverec s „M“, značka v horní liště CSS čtverec s písmenem M a navbar nesl třetí, ručně vloženou kopii cest hranolu. - Dva kontrakty motivu. Web ukládal volbu čtenáře pod
color-themez Flowbite; Cockpit podmj-theme; každý dodával vlastní příkaz před vykreslením. - Velikosti byly číslice v hranatých závorkách. Stylesheet Cockpitu nastavoval typ v
text-[13.5px],text-[11px],text-[10px],text-[12px],text-[12.5px],text-[11.5px]a rozestupy popisků vtracking-[0.1em],[0.12em],[0.14em],[0.16em]; stránky reportů, které spustitelný soubor vykresluje, nastavovaly15px/1.55,62rem,.5rem. Nic z toho nebylo rozhodnutí, které by někdo mohl najít, změnit nebo vysvětlit. - Nic nedokázalo vysvětlit token. Deklarace byla data, která shellový skript přečetl jednou. Žádná schopnost, trasa, nástroj MCP ani stránka Cockpitu nemohly říct, k čemu
--mj-text-faintslouží, na co se rozlišuje v tmavém motivu ani které povrchy jej čtou.
Samotný generátor byl bash skript s vloženým Python parserem. Za běhu se na nic nedal zeptat, nešlo ho otypovat ani opřít o schéma a duplikoval podmnožinu YAML, kterou už spustitelný soubor parsuje.
Rozhodnutí
Design systém je typovaný model v crate, share/design/tokens.yaml je jeho jediná deklarace, majordomus generate design zapisuje každou projekci a každý first-party povrch konzumuje projekci a o ničem nerozhoduje.
Concretely:
- Model.
crate::design::DesignSystemčte deklaraci přes vlastní podmnožinu YAML vrstvy, zachovává pořadí deklarace, odmítá nekonzistenci (roli jmenující neexistující položku palety, slovo stavu zařazené pod dva významy, alias ničeho, hodnotu, kterou by parser CSS tiše zahodil), vytváří svůj otisk a odpovídáexplain. Spustitelný soubor nese generovanou kopii deklarace, takže jeho odpovědi jsou deklarací, z níž vzešly jeho stylesheety, a crate se sestaví i při samostatném balení. - Sémantické role nad surovou paletou. Deklarace drží surovou paletu — hodnoty Tailwindu, citované, doložené jako shodné s nainstalovaným Tailwindem pomocí
scripts/cockpit-assets— a čtrnáct rolí nad ní (bg,raised,sunken,line,fg,body,muted,faint,accent,accent-strong,accent-soft,accent-line,accent-fill,on-accent), každou s položkou světlé a tmavé palety. Konzument čte--mj-<role>; paletu nečte nic. - Slovník webu je alias. Každé jméno z Flowbite, které šablony webu používají, je deklarováno ve vygenerovaném tématu jako
var(--mj-<role>), po vlastním tématu Flowbite a jeho bloku.dark, takžetext-headinga--mj-fgjsou v obou tématech jedna hodnota. Šablony si ponechávají svůj slovník; hodnoty se přesouvají pod vlastnictví deklarace. Hodnoty rolí jsou vlastní hodnoty webu — výchozí hodnoty Flowbite, pojmenované — takže se publikovaný web nehýbe; Cockpit se k němu přibližuje, včetně tmavého tématu. - Stav je jeden slovník. Pět významů (
ok,warn,bad,info,neutral), každý s textem, pozadím a ohraničením pro každé téma; a každé slovo stavu, které kterýkoli povrch vykreslí, zařazené pod jeden význam. Vygenerovaný stylesheet stavů emituje jednu skupinu selektorů na slovo (.mj-badge--succeeded,.mj-alert--stale,.mj-status--live), která nastavuje tři nepřímé vlastnosti; sdílené primitivy odznaku, upozornění a stavu je čtou. Slovo je obarveno významem, pod který je zařazeno, a ničím jiným. Makra odznaků webu,badge()Cockpitu a skripty všechny emitujímj-badge--<word>; kontrola webu a sonda Cockpitu odmítnou slovo, které deklarace nezařazuje. - Sdílené primitivy. Komponenty, které vykreslují oba povrchy — badge, tag, alert, card, table, facts, chips, link, mono, kbd, blok kódu, details, ovládací prvky formulářů, swatch — žijí v
share/design/primitives.css, importované oběma vstupními body Tailwindu vedle sdílené přístupné báze (focus ring, omezený pohyb). Stylopis Cockpitu si ponechává to, co má jen Cockpit: shell, paletu, runner, log, canvasy. - Typ písma, rozvržení, rádius a pohyb jsou pojmenovány. Čtyři kroky (
meta,label,small,dense) se stávají nástrojitext-<step>a vlastnostmi--text-<step>; jedno řádkování, tři hodnoty rozvržení, tři rádiusy a jedna doba trvání stejně. Velikost v pixelech v závorkách ve stylu prvního dodavatele selže na kontrole. - Jeden kontrakt tématu a jedna značka. Třída, která znamená tmavý, a klíč úložiště jsou dvě hodnoty v deklaraci; příkaz před vykreslením se z nich generuje a web i Cockpit jej dodávají identicky; skripty čtou obojí ze stránky. Značky žijí pod
share/design/brand/; každá kopie, kterou povrch servíruje — favicon a zkompilovaná značka Cockpitu, favicon a obrázky webu — je generovaný artefakt téhož souboru a kontrola je porovnává bajt po bajtu. - Introspekce je odvozená. Modul schopností
design(design.system,design.tokens,design.explain) se promítá do MCP, HTTP, OpenAPI a stránky Cockpitu jako každý jiný modul; stránka Design v Cockpitu vykresluje model se vzorníky pro oba motivy;docs/generated/design.{json,yaml,md}je inventář; dataset webu nese slovník, kontrakt a šířky auditu. - Drift se ptá na stránce. Každý vygenerovaný list nese
--mj-design: "<twelve hex>", otisk deklarace; spustitelný soubor vtiskne tentýž otisk na každou stránku, kterou vykreslí. Cockpit při načtení oba porovná a řekne to; sonda webu a sonda Cockpit tvrdí, že se stylopis, stránka a spustitelný soubor shodují — smluvní test napříč povrchy, v prohlížeči, v šířkách, které deklarace jmenuje.
Důsledky
Web a Cockpit nyní řeší každou barvu, typografický stupeň, význam stavu, kontrakt tématu a značku z jednoho souboru a změna tam dosáhne obou — i stránek reportů a shellu Swagger — po majordomus generate design následovaném dvěma buildy aktiv. Přidání role nebo stavového slova je jedna úprava deklarace: stylesheety, dataset webu, inventář, explain i stránka Cockpitu ji nesou, aniž by se dotklo kteréhokoli spotřebitele. test/cases/109_design_system.sh a testy vykreslování to dokazují tím, že to dělají.
Chování, které se záměrně pohnulo:
- Tmavý motiv Cockpitu je nyní motivem webu (stránka gray-950, zvýšené gray-900, jamky a linky gray-800, text gray-400, nadpisy bílé). ADR 0031 ponechal tmavou paletu s odstínem 250 jako „lepší z obou“; mandát jmenuje web jako referenci a protože se ta paleta nikdy fakticky neuplatnila, nic, co člověk viděl, se neztrácí.
- Tělo textu Cockpitu je čtecí šeď webu; nadpisy, identifikátory a hodnoty si ponechávají silnou barvu textu, což je vlastní uspořádání webu.
- Barvy stavů jsou odstíny webu (emerald, orange, rose, blue v odstínech Flowbite); odznaky Cockpitu green-700/amber-700/red-700 odcházejí.
- Typografické stupně jsou čtyři pojmenované velikosti; 12.5px se stává 12px a 11.5px se stává 11px.
- Klíč motivu je všude
color-theme; čtenář, který si zvolil motiv Cockpitu podmj-theme, volí ještě jednou.
Co bylo odstraněno: scripts/design-tokens, vlastní favicon a písmenná značka Cockpitu, adresář assets/, vložená kopie značky v navbaru, aliasová vrstva --mj-graph-* a --mj-text-*/--mj-bg-*/--mj-border (skripty čtou role přímo), každá surová paletová utilita a závorková velikost ve stylesheetu Cockpitu, stylový blok x-cloak v hlavičce webu a obě ručně psané pre-paint deklarace.
Zvažované a zamítnuté alternativy:
- Zkopírovat CSS webu do Cockpitu. Web jsou utility Flowbite v šablonách; Cockpit jsou sémantické třídy nad tokeny. Kopírování by dalo dva stylopisy, které dnes vypadají stejně a zítra nemají společného vlastníka.
- Jedna velká sdílená stylesheet pro oba. Oba povrchy potřebují různé hustoty, rozvržení a komponenty; sjednocení by byl třetí design, který by žádný povrch nepoužil celý.
- Ponechat obě implementace a porovnávat screenshoty. Pixelové baseline jsou na různých strojích křehké a dokazují shodu až zpětně; sdílená deklarace činí shodu strukturální a kontrakt otisku ji dokazuje v prohlížeči bez obrázku.
- Tokeny deklarované nezávisle na povrch, držené shodné testem. Test, který přirovnává dvě kopie, je test, který někdo aktualizuje dvakrát; druhá kopie je vada.
- Přepsat šablony webu ze slovníku Flowbite na třídy
.mj-*. Šest set výskytůtext-headingbez vizuálního zisku; alias činí slovník synonymem, což je vše, co vlastnictví vyžaduje. - Ponechat shellový generátor. Nešlo ho otypovat, nešlo se ho za běhu na nic zeptat a duplikoval parser, který crate už má.
Ponecháno nehotové a pojmenováno: vlastní strom komponent Swagger UI si drží stylesheet třetí strany; jména Flowbite na webu, která nejsou sdílenými rozhodnutími (neutral-tertiary, brand-strong, varianty fg-* bez zvýraznění), si drží hodnoty Flowbite; audit prohlížeče (scripts/ui) stále objevuje jen povrchy ve statickém adresáři, takže přístupnost Cockpitu měří jeho vlastní sonda, ne kontrolní průchod kontrastu v auditu.