Přeskočit na obsah

Tato stránka byla automaticky přeložena z angličtiny, jediného jazyka, ve kterém se tento web píše. Číst originál.

Designový systém

design systém: jediná deklarace, z níž se vykresluje každý povrch, pipeline od `share/design/tokens.yaml` k webu, Cockpitu a vlastním stránkám executable, jak přidat roli, stavové slovo nebo krok typografie bez zásahu do spotřebitele, jak se chytá drift a jak číst stránku, která vypadá špatně

Vykresleno z docs/DESIGN_SYSTEM.md — stejného Markdownu, který ukazuje GitHub.

Jak se web publikovaný na GitHub Pages, Cockpit, stránky, které vykresluje sám spustitelný soubor, a shell Swaggeru začnou jevit jako jeden nástroj: kde se rozhoduje o vizuální identitě, jak dosáhne každého povrchu, jak se rozšiřuje, jak se zachytí drift a co dělat, když stránka vypadá špatně. Chování tak, jak je implementováno a testováno; kde se implementace a tento dokument rozcházejí, má chybu dokument a mění se ve stejném commitu jako oprava. Pravidlo je project.design-tokens-declared-once@2; rozhodnutí jsou ADR 0031 a ADR 0036.

Proč

HTML před člověka staví čtyři povrchy a každý si vyrostl vlastní design. Web byl výchozí motiv Flowbite nad paletou Tailwindu; Cockpit druhý build Tailwindu s vlastní paletou, tmavým motivem v jiném odstínu (který se, jak se ukázalo, nikdy neuplatnil), vlastní favikonou, vlastním klíčem motivu a šesti pixelovými velikostmi v závorkách; stránky reportů sedm hexadecimálních literálů; shell Swaggeru to, co dodala třetí strana. Stavy měly tři palety a to, které slovo znamená který stav, bylo zapsáno v seznamech selektorů stylopisu a ve čtyřech makrech šablon. Nic nedokázalo říct, k čemu je --mj-text-faint.

Nic z toho nebyla špatná hodnota. Byl to návrh bez kanonického domova, takže ho každý povrch rozhodoval znovu a žádná kontrola nedokázala odlišit poslušnost od vymýšlení.

Zdroj pravdy

share/design/tokens.yaml. Jeden soubor ve vlastní podmnožině YAML vrstvy, který drží:

sekceo čem rozhoduje
identitynázev a značky pod share/design/brand/
fontsystémový zásobník a monospace zásobník
palettesyrové barvy — z Tailwindu, citované; interní, spotřebitel je nikdy nečte
rolessémantický povrch: bg, raised, sunken, line, fg, body, muted, faint, accent, accent-strong, accent-soft, accent-line, accent-fill, on-accent, každý položka palety pro každé téma
status.rolespět významů — ok, warn, bad, info, neutral — každý s textem, pozadím a ohraničením pro každé téma
status.statesslovník: každé slovo stavu, které kterýkoli povrch vykresluje, zařazené pod jeden význam
tonesodstíny pro identitu a nikdy význam — modrá, fialová, zelená, oranžová, růžová, azurová — každý s inkoustem, pozadím, ohraničením a výplní pro každý motiv
typepojmenovaná škála (meta, label, small, dense) a prostrkání písmen
layout, radius, motionsdílené míry, poloměry rohů a trvání
themetřída, která znamená tmavý, a klíč localStorage podle volby čtenáře
viewportsšířky, při nichž měří každý audit prohlížečem
alias.flowbiteslovník webu, každý název z Flowbite je synonymem role nebo stavové barvy

Crate ho čte jako typovaný model (apps/majordomus-cli/src/design/): pořadí deklarací se zachová, každý odkaz se vyřeší, každá nekonzistence se odmítne dřív, než se cokoli zapíše, a nad kanonickou podobou se spočítá otisk SHA-256. Prvních dvanáct číslic tohoto otisku nese každý stylesheet jako --mj-design.

Pipeline

flowchart TD
  yaml["share/design/tokens.yaml"]
  subgraph sheets["share/design/"]
    theme["theme.css<br>Tailwind @theme + Flowbite's names<br>as var(--mj-*)"]
    surface["surface.css<br>every role and status colour,<br>light and dark"]
    status["status.css<br>one selector group per state word"]
    base["base.css<br>(hand-written) focus ring, reduced motion"]
    primitives["primitives.css<br>(hand-written) badge, table, card,<br>code, form …"]
  end
  tailwind["site/tailwind.css"]
  appcss["site/static/app.css"]
  pages["GitHub Pages"]
  cksrc["share/cockpit/src/cockpit.css"]
  ckcss["share/cockpit/cockpit.css"]
  cockpit["Cockpit"]
  webtokens["apps/majordomus-cli/src/web/tokens.css<br>the same tokens, no Tailwind"]
  webout["reports, home page, Swagger shell"]
  decl["apps/majordomus-cli/src/design/tokens.yaml<br>the declaration, compiled in"]
  declout["design.* capabilities,<br>Cockpit shell and Design page"]
  logo["apps/majordomus-cli/src/cockpit/logo-mark.svg<br>the mark, compiled in"]
  topbar["the UI's hero mark"]
  brand["share/cockpit/favicon.svg, site/static/favicon.svg,<br>site/static/images/*.svg<br>every copy of the brand"]
  registry["site/data/registry/design.json<br>vocabulary, theme contract, widths"]
  registryout["templates, site-probe, ui audit"]
  inventory["docs/generated/design.json, design.yaml, design.md<br>the inventory"]
  yaml -->|"majordomus generate design (crate::design::render)"| sheets
  sheets --> tailwind
  tailwind --> appcss
  appcss --> pages
  sheets --> cksrc
  cksrc --> ckcss
  ckcss --> cockpit
  webtokens --> webout
  decl --> declout
  logo --> topbar
  registry --> registryout

Dva ručně psané soubory leží vedle generovaných a importují je oba vstupní body Tailwindu: base.css (přístupné výchozí hodnoty) a primitives.css (komponenty .mj-*, které vykreslují obě plochy). Jmenují tokeny a nic nevolí. Třetí, figure.css, je jen webu: rám prohlížeče diagramů a grafu (DIAGRAMS.md) nad stejnými tokeny a dvojicemi, které už primitiva používají. Je záměrně bez vrstvy, protože rám je vlastní <pre> diagramu a musí zvítězit nad jakýmikoli utilitami, které mu stránka dala.

Oba buildy Tailwindu importují generované styly ve stejném pořadí, po motivu Flowbite. Web drží ve svých šablonách slovník Flowbite — text-heading, border-default, bg-brand — a generovaný motiv deklaruje každé z těch jmen jako var(--mj-<role>), bez vrstvy a po vlastním bloku .dark Flowbite, takže jméno je synonymum a hodnota je v obou motivech hodnotou deklarace. Cockpit čte --mj-* přímo. Stránka reportu a shell Swaggeru kompilují stejné tokeny dovnitř a nic nestahují.

Stav funguje přes jednu nepřímost. Generovaný stylopis stavů vydává pro každý význam jednu skupinu selektorů nad významem a každým slovem k němu zařazeným — .mj-badge--succeeded, .mj-alert--succeeded, .mj-status--succeeded, … — nastavující --mj-status-fg, --mj-status-bg a --mj-status-line; sdílené primitivy odznaku, upozornění a stavu čtou tyto tři s neutrální záložní hodnotou. Povrch tedy píše mj-badge--<word> a nic jiného a barva je taková, pod čím je slovo zařazeno.

Nic se nespouští ručně. scripts/derive (a just derive) spouští majordomus generate, což zahrnuje cíl designu; scripts/cockpit-assets a scripts/site-build kompilují dva balíky Tailwind z generovaných stylů; hook pre-commit a plán CI odmítnou strom, jehož projekce jsou zastaralé.

Jak ji rozšířit

Každé rozšíření je jedna úprava v share/design/tokens.yaml, pak majordomus generate design (nebo just derive) a nakonec obě sestavení assetů. Nic dalšího se neupravuje.

  • Sémantická barva. Přidejte roli pod roles s about, light a dark pojmenovávajícími položky palety (přidejte položky pod palette, pokud je barva nová — hodnota Tailwindu, v uvozovkách). Je to --mj-<name> na každém povrchu, třída swatche pro inspektor, řádek na stránce Design v Cockpitu a v soupisu, a odpovídá na to explain.
  • Stavová sémantika. Přidejte význam pod status.roles s fg, bg a line na motiv a zařaďte jeho slova pod status.states. Každé mj-badge--<word> je ostylováno.
  • Odstín. Přidejte jej pod tones s fg, bg, line a fill pro každý motiv. Odstín je barva pro identitu — krok posloupnosti, karta jednoho povrchu — a nikdy verdikt, proto to není stav. .mj-tone--<name> nastaví čtyři vlastnosti --mj-tone-*, které čte každá komponenta sady, měření kontrastu páruje odstín jen sám se sebou a site/data/registry/design.json jej uvádí pro šablony.
  • Stavové slovo. Přidejte ho do seznamu pod jeho význam. To je celá změna: test/cases/109_design_system.sh dělá přesně toto, aby prokázal, že se nic jiného nepohne.
  • Krok typografie. Přidejte ho pod type.scale s size a leading; text-<name> existuje v obou sestaveních Tailwindu a --text-<name> na každé stránce.
  • Hodnota odsazení nebo rozvržení. Přidejte ji pod layout; je --mj-<name>.
  • Primitivum komponenty. Napište ho jednou v share/design/primitives.css nad tokeny a kroky stupnice; mají ho oba povrchy. Vlastní stylesheet Cockpitu drží jen to, co má samotný Cockpit.
  • Značka. Nahraďte soubor pod share/design/brand/; každá kopie se vygeneruje znovu.
  • Název z Flowbite, který web používá a který by měl sledovat roli. Přidejte jej pod alias.flowbite. Název, který tam chybí, si ponechá hodnotu Flowbite, což je prohlášení, že nejde o sdílené rozhodnutí.

Proces nikdy nezahrnuje úpravu šablon webu a stylopisu Cockpitu ze stejného důvodu. Pokud ano, architektura má díru; založte na to úkol.

Sada

Komponenty, z nichž jsou složeny produktové a dokumentační stránky webu: hero a citát doktríny, panely a fáze, strom souborů, body, řádky rozhraní, dlaždice odkazů, pás tvrzení, terminál se strukturovaným výstupem, příkaz s tlačítkem kopírování, pruh karet ve třech podobách, řádky HTTP API, okno dashboardu s počty, grafem, kanálem, akcemi a seznamem zdraví, stopa, vyhledávací pole, obsah, tok kroků a tři diagramy — zásobník vrstev, vějíř, který spojuje řádky s hubem, a cyklus o čtyřech. /kit/ vykresluje každou z nich z dat tohoto repozitáře.

souborco drží
share/design/kit.cssjak každá komponenta vypadá, jako třídy .mj-* nad rolemi, nepřímostí stavu a nepřímostí tónu; měřeno kontrolou kontrastu jako primitiva
site/templates/kit/*.htmlco je každá komponenta, jako komponenta Tera: base (icon, glyph, step, button, pill, state, copy), content (hero, quote, section, panel, stages, tree, points, routes, tiles, links, strip, figure, claims, table), code (terminal, term_line, term_out, term_box, cmd, cmd_list, tabs, tab_panel, method, request, endpoint, listing), dashboard (window, sidenav, kpis, chart, legend, feed, actions, health), docs (trail, search, toc, flow), diagrams (stack, fan, cycle)
site/data/kit/icons.tomlobrysové glyfy, které komponenta jmenuje klíčem; neznámý klíč shodí build
site/kit.jstři chování, která značkování neunese: kopírovat, přepínat karty, označit čtenou sekci
site/kit.tailwind.css, site/templates/partials/kit-assets.htmlvlastní sheet a skript sady, které si stránka zvolí

Konvence, které komponenta dodržuje, aby je stránka mohla skládat, aniž by četla jejich zdroj:

  • Položka je mapa a klíč znamená všude jedno. title a text se čtou, icon je klíč sady ikon, tone tón, href kam vede, state slovo stavu.
  • V komponentě se nezapisuje žádná hodnota. Počet, příkaz, přepis nebo release jí předá stránka a stránka ho čte z generované datové sady. Čísla v mockupu nejsou data.
  • Stav je atribut. Vybraná karta je aria-selected, aktuální odkaz aria-current, otevřená skupina details[open]. Stylesheet stylizuje atribut, takže žádná vázaná třída nemůže prohrát se statickou utilitou (viz Alpine :class prohrává se statickými utilitami).
  • Nic nevyžaduje, aby se skript četl. Tlačítko kopírování a lišta karet se vykreslí hidden a odhalí je site/kit.js; každý panel karty je vykreslen pod nadpisem, který by jeho karta nesla; rozbalovací blok je nativní <details>; křivky vějíře počítá šablona podle toho, kolik řádků má každá strana.
  • Sada je volbou stránky. Stránka z ní složená zahrnuje {% block kit %}{% include "partials/kit-assets.html" %}{% endblock kit %}; každá jiná stránka nenačítá nic z ní, což drží domovskou stránku v jejím rozpočtu bajtů.

Obrazovky. Obrazovka je celá stránka složená jen ze sady, která vykresluje jeden z návrhů stránek vlastníka (architektura, integrace, dokumentace, CLI, Cockpit, funkce, jak to funguje, úvod) z dat tohoto repozitáře v /kit/<screen>/. Šablona obrazovky (site/templates/screens/<name>.html) rozšiřuje site/templates/screen.html, která načítá datové sady, jež obrazovka smí číst, pod jednou sadou jmen — design, registry, kinds, oas, cl, term, cli, commands, tones, base — a její stránka je site/content-src/kit/<name>.md. Návrhy nesly hodnoty, které nikdo neměřil (verzi, která nikdy nevyšla, počty neexistujících objektů, latence, číslo úkolu, port); obrazovka neukazuje žádnou z nich a test/cases/791_a_screen_is_composed_of_the_kit_and_of_real_data.sh odmítne tu, která ano, tu, která si volí vlastní barvu, styl nebo třídu, i tu mimo rámec.

Přidání komponenty: napište její třídy do share/design/kit.css, její markup do souboru site/templates/kit/, do něhož patří, a příklad na site/templates/kit-page.html ze skutečných dat; ovládací prvek, který nese, potřebuje specifikaci pod scripts/lib/interaction-specs/. test/cases/790_the_kit_is_one_vocabulary_and_its_page_renders_all_of_it.sh odmítne komponentu, kterou stránka nevykresluje, třídu, kterou nevydává žádná šablona nebo nedefinuje žádný sheet, ikonu, kterou sada nemá, a sadu načítanou každou stránkou; scripts/interaction-probe projde v prohlížeči každé tlačítko kopírování, záložku a pole hledání. Tera 2 v Zole 0.23 typuje argument komponenty podle jeho výchozí hodnoty, bere literální argument jen ve složených závorkách (count={4}), povoluje jedno for na comprehension a pole hluboká dvě úrovně; sada je napsána v těchto mezích.

Navigační lišta: web je reference

majordomus.dev je referencí toho, jak produkt vypadá a chová se, a povrchy, které program servíruje, se z něj odvozují, místo aby ho napodobovaly (project.the-ui-wears-the-sites-navbar). První odvozená věc je lišta v horní části stránky. Má jednu deklaraci (site/data/nav.toml, project.navigation-is-declared-once) a jeden renderer (site/templates/partials/navbar.html); každá obrazovka majordomus ui a každá stránka Cockpitu nosí výstup tohoto rendereru:

  • značkování je lišta sestaveného webu, vyříznutá scripts/ui navbar --write do apps/majordomus-cli/src/ui/navbar.html a zkompilovaná do spustitelného souboru — s release v odznaku ponechaným k doplnění spustitelným souborem a s vyřazenými komentáři buildu;
  • styly jsou tytéž utility nad týmiž tokeny: vstupy Tailwind pro UI a Cockpit (share/ui/src/ui.css, share/cockpit/src/cockpit.css) importují to, co importuje vstup webu, a kopii jmenují jako zdroj, takže každý zkompilovaný stylesheet nese přesně ta pravidla, v nichž je lišta napsána;
  • chování je vlastní nav.js a theme.js webu, servírované z share/ui/ a share/cockpit/ jako bajtové kopie, které scripts/cockpit-assets --check drží shodné s originály.

scripts/ui navbar (spouští ji jako první scripts/ui audit, kontrola ui-audit) odmítne kopii, která není sestavenou lištou, a v prohlížeči porovná lištu na úvodní stránce webu s lištou na /ui a lištu na /cockpit stejně — tag každého prvku, třídy, text, box a vypočtené styly, světlý i tmavý režim, při každé šířce, kterou viewports deklaruje, zavřenou i s otevřenou nabídkou. Spustitelný soubor rozhoduje jen o tom, co ví sám: o verzi a o tom, že položka produktu vede do samotného UI, a ne k jeho publikovanému snímku. Zbytek říká příručka UI (MAJORDOMUS_UI.md).

Validace

cokdekdy
deklarace je konzistentnícrate::design::DesignSystem::validate, spouštěno generatedříve, než se cokoli zapíše
každá projekce je aktuálnímajordomus generate --check (rust-integration / generation-converges)každý plán, který se dotýká crate, share nebo generovaného stromu
žádný zdroj nenese vlastní designové rozhodnutíscripts/ci/design-check (kontrola design, job structure)každý plán, který se dotýká stylopisu, šablony, skriptu nebo webového kódu crate
zkompilovaný stylesheet Cockpitu odpovídá svému zdroji a paleta odpovídá nainstalovanému Tailwinduscripts/cockpit-assets --check (kontrola cockpit-assets)job webu
každé slovo odznaku na webu je zařazeno a každá stránka nese otiskscripts/site-check (kontrola site-build)job webu
stylesheet, stránka a datová sada se shodují při každé deklarované šířcescripts/site-probe (kontrola site-probe)job webu
stylopis, stránka a spustitelný soubor se shodují; každé slovo odznaku je zaevidováno; žádné přetečení při každé deklarované šířcescripts/cockpit-probe (kontrola cockpit-probe)úloha cockpit
každá stránka je ovladatelná klávesnicí a prochází kontrastem při každé šířcescripts/ui audit (kontrola ui-audit)job webu
navbar UI je navbar webu: zkompilovaná kopie je sestavená lišta a obě lišty se shodují prvek po prvku a styl po styluscripts/ui navbar, spouštěno jako první pomocí scripts/ui audit (kontrola ui-audit)job ui
každá barva, kterou deklarace spáruje s pozadím, je na něm čitelná, v obou motivechcrate::design::contrast přes design.contrast, otázka 9 pro scripts/ci/design-check; test/cases/120_design_contrast.shkaždý plán, který se dotýká deklarace nebo primitiva
model, renderer a vlastnost nulové registracetesty crate; test/cases/107_design_tokens.sh; test/cases/109_design_system.shúlohy rust a suite

Otázky kontroly jsou nalezené způsoby selhání: barevný literál, syrová utilita palety (text-green-700), velikost v hranatých závorkách (text-[11px]), druhá deklarace kanonické vlastnosti, čtení tokenu --mj-, který nic nevydává, kopie značky, která se liší od kanonického souboru nebo žije jinde, a ručně pojmenovaný klíč nebo třída tématu. Každá výjimka v kontrole je důvod, uvedený tam, kde je kontrola napsána.

Poslední otázka se netýká toho, kde se rozhodlo, ale zda je rozhodnutí čitelné. Které popředí se vykresluje na kterém pozadí, se odvozuje — deklarace přiřazuje text každého stavu jeho vlastnímu pozadí a primitiva říkají zbytek: pravidlo nastavující barvu a pozadí uvádí dvojici, pravidlo nastavující jen barvu uvádí popředí, které dopadá na každé pozadí, jaké kontejner nastaví — takže role přidaná zítra se změří zítra a žádný seznam se nemusí udržovat. Práh je WCAG 2.1 AA, 4,5:1 pro text, stejná konstanta, kterou scripts/lib/ui-contrast.mjs drží pro generované zvýrazňovací palety; ohraničení se měří proti 3:1 a hlásí se, ale nevynucuje, protože primitivum, které jej kreslí, vždy ukazuje vedle něj slovo.

Introspekce

povrchjak
MCPmajordomus_design, majordomus_design_tokens, majordomus_design_explain, majordomus_design_contrast; zdroj majordomus://design
HTTPGET /api/v1/design, /api/v1/design/tokens?kind=role, /api/v1/design/explain?token=fg, /api/v1/design/contrast
OpenAPI, Swaggerstejné čtyři operace, z registru
Cockpit/cockpit/design: každá role s vzorky obou motivů, každý stav se svými slovy jako odznaky, typová škála ve vlastních velikostech, projekce — a odznak, který říká, zda stylesheet načtený stránkou souhlasí se spustitelným souborem
referencedocs/generated/design.md a JSON a YAML vedle něj

explain bere roli, stav, slovo stavu, krok typu, položku palety nebo vlastní vlastnost, kterou se kterákoli z nich stane:

majordomus_design_explain {"token": "--mj-ok"}
→ kind: status, about: healthy, done, passing …,
  parts: fg --mj-ok emerald-900 / emerald-300, bg --mj-ok-bg …, line --mj-ok-line …,
  states: ok pass passed succeeded completed done …,
  aliases: --color-fg-success-strong --color-success-soft --color-success-subtle,
  projections: share/design/theme.css share/design/surface.css …

Řešení problémů

  • Stránka na jednom povrchu vypadá špatně. Přečtěte --mj-design ze stránky (getComputedStyle(document.documentElement).getPropertyValue('--mj-design')) a porovnejte s GET /api/v1/design (design) nebo site/data/registry/design.json. Cockpit to dělá sám a při neshodě ukáže upozornění. Odlišný otisk znamená stylesheet zkompilovaný dřív, než se deklarace posunula: spusťte majordomus generate design, poté scripts/cockpit-assets nebo scripts/site-build a restartujte server.
  • Chybí barva. Nedeklarovaná vlastní vlastnost je prázdný řetězec, nikdy chyba. scripts/ci/design-check jmenuje každou --mj-, kterou nic nevysílá; spusťte to.
  • Odznak je šedý, a neměl by být. Jeho slovo není zařazeno pod žádný stav. explain ho; pokud nic neodpoví, zařaďte slovo pod status.states. scripts/site-check a scripts/cockpit-probe nezařazené slovo odmítnou.
  • Cockpit nemá styly. share/cockpit/cockpit.css chybí nebo je zastaralé: scripts/cockpit-assets. Shell to oznámí v banneru.
  • Tmavý režim se nepřepíná. Třída, kterou stránka přepíná, je theme.class z deklarace, zapsaná na <html> (web) nebo <body> (Cockpit) jako data-theme-class; skripty ji čtou tam. Stránka bez toho atributu má neplatnou zkompilovanou deklaraci: GET /api/v1/design říká proč.
  • Selže sonda vizuální regrese. Zjištění jmenuje routu, šířku a tvrzení. Přetečení je vada rozvržení na té stránce; zjištění design je zastaralý bundle nebo stránka, která nenese data-design; zjištění font znamená jiný stack, než byl deklarován — obvykle stylesheet třetí strany načtený po našem.
  • generate deklaraci zamítne. Zpráva pojmenuje cestu klíče a důvod (roles.sunken.light: 'gray-51' is not a palette entry). Nic nebylo zapsáno.

Co se záměrně nesdílí

Pod lištou sdílejí povrchy jazyk, ne layout: samotná lišta je webová i na UI a v Cockpitu (výše). Web tvoří komponenty Flowbite v šablonách o hustotě pro čtení; Cockpit jsou sémantické třídy nad týmiž tokeny o provozní hustotě. Vlastní strom komponent Swagger UI si ponechává stylesheet třetí strany uvnitř rámu, který je náš. Názvy Flowbite, které web používá a které nejsou sdílenými rozhodnutími (neutral-tertiary, brand-strong, varianty fg-* bez strong), si ponechávají hodnoty Flowbite, dokud je někdo nezařadí pod roli.

Prohledává názvy a popisy všech stránek, které tento web publikuje.