Review ACRM-76…81 — rodina Button: nálezy, bug list, změna postupu

Datum: 2. 9. 2026 večer · reviewer: Claude (jako senior UX/UI) · zadal Štěpán (AD) Předmět: 6 picků na stránkách /ds/components/button/* po pilotu fáze 1. Každý nález ověřen proti kódu na origin/main acrm (klon /root/acrm-main), ne proti dojmu.

0. Shrnutí

Všech šest připomínek je oprávněných a žádná není kosmetická. Společný kořen: pilot byl postaven „aby prošel", ne „aby byl pravdivý" — registr vzal za podkomponenty věci, které komponentami nejsou (Kbd hint, Save button, Split button), stories ukazovaly stavy, které v kódu neexistují (disabled ikonového tlačítka), a kompozice porušila pravidla komponenty, kterou skládá (Section top bar zakazuje „N z M"). Web to navíc zakryl: koláčky coverage svítily ✓ i u vypůjčené story, a chybějící příklad nahradil celým legacy mockupem.

Z 5 podkomponent rodiny Button jsou skutečné 2 (Button, Icon button). Zbylé tři jsou: vlastnost Buttonu (kbd), duplicitní bespoke tlačítko v Dialeru (.dlr-save) a mockup bez implementace (split). V appce jsou navíc tři další ikonová/tlačítková provedení mimo kanon (.pr-iconbtn, .au-btn, .est-btn, .rvsw-btn, .toast-btn, .sk-btn, raw .acrm-icobtn v 15 souborech) a dva styly kbd (.acrm-kbd vs .as-search kbd).

1. Nálezy per ticket (ověřeno v kódu)

ACRM-76 · Kbd hint — „chybí keyboard hint, jen stejná sada tlačítek"

  • Fakt: .acrm-kbd (inputs.css:79) se v appce používá jen přes <Button kbd="…"> (35 míst) + DsInputsPage. Není to komponenta, je to slot Buttonu. Registr ho vedl jako samostatnou podkomponentu se stories components--buttons (vypůjčená legacy story) → stránka ukazovala tlačítka bez jediného kbd.
  • Duplicita: .as-search kbd (app-shell.css:170) je druhý, jinak nastylovaný kbd (600 11px mono, --border, surface-75) vs .acrm-kbd (500 11px sans, --border-strong, 19px). Dva vizuály pro jednu věc.
  • Verdikt: podkomponentu zrušit; kbd = sekce „With icon & kbd" u Buttonu + řádek v Text field (search input ⌘K). Sjednotit .as-search kbd na .acrm-kbd.

ACRM-77 · Save button — „příklad kompletně špatně; proč je to zvlášť"

  • Fakt: .dlr-save je raw <button className="dlr-save"> v DialerPage.tsx:1587 — jediný výskyt. Vlastní CSS (dialer.css:672): full-width, --control-h, zelená, vlastní :disabled. Je to Button, který nikdo nepostavil z Buttonu. Registr ho zapsal jako „canon/app-css" a web, protože neměl story, vložil celý legacy mockup Dialer.html jako „příklad".
  • Verdikt: podkomponentu zrušit. Rozhodnutí AD: zelená completion akce (ratifikováno 13. 8. v redesignu dialeru) → buď Button variant="success" (nová varianta v kanonu, jediné povolené použití = completion rychlostního formuláře), nebo variant="primary" + w-full. Poté .dlr-save smazat.

ACRM-78 · Icon button — velikosti: „není vidět rozdíl, vždy stejná ikona"

  • Fakt: size="sm" = 24 px vs default --control-h 30 px — rozdíl 6 px, ve story s třemi různými ikonami (settings / x / dots) nerozpoznatelný. Třetí položka „iconSize 18" není velikost tlačítka, ale glyfu. Navíc: size="sm" má v appce 0 použití přes komponentu, raw .acrm-icobtn.sm 2 použití → varianta prakticky mrtvá.
  • Duplicita: .pr-iconbtn (person-record.css:149; 32×30, rámeček --border-strong, --shadow-xs, 9 souborů) je orámované ikonové tlačítko mimo balíček — komponenta o něm ví jen komentářem („řeší se zvlášť"). Raw .acrm-icobtn v 15 souborech vs <IconButton> v 19.
  • Verdikt: Sizes = stejná ikona, obě velikosti vedle sebe s px, bez iconSize; rozhodnout osud sm (smazat, nebo dát reálné použití); .pr-iconbtn → IconButton variant="framed"; raw .acrm-icobtn migrovat na komponentu.

ACRM-79 · Icon button — „rest a disabled se vůbec neliší"

  • Fakt: .acrm-icobtn (ds.css:195–218) má jen base, :hover, .sm. Žádné :disabled, žádné [aria-pressed]/open, žádné :active. Story „disabled" tedy vykreslila totéž co rest — story řekla pravdu o chybě v appce, ale prezentovala ji jako stav. Focus jde z globálního :focus-visible (ds.css:2805) — to je v pořádku.
  • Verdikt: app bug. Doplnit :disabled (--fg-muted, cursor:not-allowed, bez hover), [aria-expanded=true]/.open (pozadí --active-surface) — tento stav appka potřebuje u kebabu a chevronů; story States pak ukáže rest · hover (simulovaně) · open · disabled.

ACRM-80 · Icon button — kompozice „rozpadlá, divná mezera, kebab má být vpravo"

  • Fakt: story složila SectionTopBar s „11 of 36" a primárním tlačítkem za kebabem. Vlastní rozhodovací tabulka SectionTopBar (SectionTopBar.tsx:23–40) říká: „ZAKÁZÁNO: count / „N z M" kdekoliv" a sloty: identita · pohledy · Spacer · akce · primaryAction. Reálný detail firmy (CompanyRecordPage.tsx:2908): identita · Spacer · ReservationControl · RecordActionsMenu (kebab) jako poslední — bez primaryAction. Mezera = prázdný slot vspace před primaryAction.
  • Verdikt: kompozice = 1:1 kopie reálné hlavičky záznamu (ReservationControl + kebab vpravo, žádný count); story nesmí vymýšlet layout, který v appce není.

ACRM-81 · Split button — „po kliknutí nic, duplicity, sloučit do Button"

  • Fakt: split button v kódu neexistuje (0 tříd, 0 použití; jen .split v legacy buttons.html). Stránka ukazovala statický legacy mockup celé karty (Add billing · Save▾ · All Companies · View settings · Filter · New record · ⋮ · ⌘K + zelený „Uložit a další") — tedy směs Buttonu, NavSelectu, Filter baru, Icon buttonu, Kbd a Save buttonu. Nic z toho není split button a nic není klikatelné.
  • Verdikt: podkomponentu zrušit (bez use-case v appce není co dokumentovat); kdyby vznikla potřeba, jde kandidátem s reálným místem použití. Legacy mockup nikdy neukazovat jako příklad.

2. Bug list — DS (web, registr, stories)

#KdeChybaOpravaRozhoduje
D1registrkbd-hint, save-button, split-button vedené jako podkomponentyodstranit; kbd → sekce Buttonu + Text field; save → varianta Buttonu (viz A2); split → nicAD jen A2
D2webExamples: „No example yet" ×5 pro každou nepoužitou sekci (šum, RULES §0)sekce bez story se nevypisuje; osnova vpravo jen z existujících—
D3webfallback „Legacy mockup" v Examples (celý screen jako příklad)odstranit; legacy jen jako odkaz v hlavičce—
D4webcoverage ✓ stories i pro vypůjčenou story (components--buttons u Kbd hint)story se počítá jen když její title odpovídá podkomponentě—
D5webiframe příkladu pevných 260 px → bílé plochyauto-výška z contentDocument (katalog je same-origin) + min 80 px—
D6web„Sizes" u Icon buttonu zobrazuje iconSize jako velikostSizes = jen rozměr kontroly; glyf do Code/props—
D7storiesIcon button Sizes: tři různé ikonystejná ikona (dots-vertical) v obou velikostech, popisek s px—
D8storiesIcon button States: disabled = restpo A1: rest · hover · open · disabled; story nesmí ukazovat stav, který CSS nemá—
D9storiesIcon button Composition: „11 of 36" + primár za kebabem1:1 hlavička záznamu (identita · Spacer · ReservationControl · kebab)—
D10storiesButton Default = legacy components--buttons + nová Default vedle sebelegacy story smazat z registru (baseline přesunout)—
D11docsusage/a11y pro kbd-hint, save-button, split-buttonsmazat / sloučit do button.usage.md—
D12procespilot prošel „coverage 5/5" a přitom byl špatněDoD doplnit o review AD před merge stories (viz §4)AD

3. Bug list — aplikace (kanon v kódu)

#KdeChybaOpravaRozhoduje
A1ds.css .acrm-icobtnchybí :disabled, [aria-expanded=true]/open, :activedoplnit stavy (disabled: --fg-muted, not-allowed, bez hover; open: --active-surface)—
A2DialerPage.tsx:1587 + dialer.css:672bespoke .dlr-save (zelený full-width) mimo ButtonButton variant="success" (nová varianta, jediné povolené použití = completion rychlostního formuláře) nebo primary + w-full; .dlr-save smazatAD
A3person-record.css:149 .pr-iconbtn (9 souborů)druhý, orámovaný ikonový button mimo balíčekIconButton variant="framed" (32×30, border-strong, shadow-xs) a migrace 9 souborů; .pr-iconbtn smazatAD (potvrdit, že framed je kanon)
A415 souborů raw <button className="acrm-icobtn">obchází komponentu (žádný type=button, různé aria)migrovat na <IconButton>; validator: pravidlo icobtn-raw (hard)—
A5app-shell.css:170 .as-search kbddruhý styl kbdnahradit .acrm-kbd; pravidlo validatoru na <kbd> bez třídy—
A6IconButton size="sm"0 použití přes komponentu, 2 rawsmazat variantu, nebo ji dát reálné místo (např. chip ×)AD
A7.acrm-btn.sm (6 použití), .acrm-btn.icon (0)CSS modifikátory bez propu, icon mrtvý.icon smazat; .sm buď prop size, nebo migrovat na --control-h a smazatAD
A8.au-btn (auth, 8 souborů), .est-btn (empty/error state), .rvsw-btn, .toast-btn, .sk-btn5 dalších tlačítkových systémůkaždý = <Button> s variantou; když variantu potřebují (např. toast link-button), přidat do kanonu, ne vedleAD u nových variant
A9SectionTopBarpravidla („žádný count", pořadí slotů) jsou jen v komentáři zdrojákupřenést do usage.md rodiny Section top bar + validator (tbar-count)—

4. Proč se to stalo a jak změnit postup

Příčiny (moje):

  1. Registr jsem nechal sestavit z názvů, ne z použití. „Kbd hint" a „Save button" vznikly z legacy karty a z toho, že třída existuje — ne z otázky „je to samostatný prvek s vlastním použitím?".
  2. Story jsem bral jako důkaz existence. Story „disabled" prošla buildem i baseline, ale ukazovala neexistující stav. Baseline zachytí regresi, ne nesmysl.
  3. Kompozice se vymýšlela, místo aby se kopírovala z appky. RULES §0.1 („portuj z reálné appky, neimprovizuj") platí i pro stories.
  4. Web hlásil hotovo dřív, než to byla pravda. Coverage ✓ z vypůjčené story, legacy mockup jako fallback, prázdné sekce jako obsah.
  5. AD viděl výsledek až po merge. Pilot šel přes mr-bot bez designového gate.

Změna postupu (návrh, platí od rodiny Text field dál):

KrokDnesNově
1 · Inventura před registremnázev + třídaTabulka použití: pro každý kandidát podkomponenty grep v appce → počet míst, soubory, screenshot 1 reálného výskytu. Bez použití = není podkomponenta (kandidát, ne kanon). Duplicity (stejný účel, jiná třída) se zapisují hned do bug listu.
2 · Stavová matice před story„udělej stories per sekci"Pro každou podkomponentu matice stav × varianta z CSS/props (rest · hover · active · focus · open · disabled · loading · selected × varianty × velikosti). Co v kódu chybí = A-bug, ne story. Story vzniká až z matice.
3 · Story = port, ne návrhvolná kompoziceComposition = kopie konkrétního místa appky (soubor:řádek v komentáři story). Sizes = stejný obsah, měnící se jen rozměr. Popisky = px/token.
4 · Design gate před mergemr-bot mergeStories MR dostane label no-auto-merge; na :3777/preview/ds-review/<rodina>.html (nebo přímo z MR katalogu) AD picky → teprve po „ok" label pryč. Jedna rodina = jedno schválení.
5 · Web nelžecoverage z existence souborůstories se počítají jen s odpovídajícím title; sekce bez story se nezobrazují; legacy nikdy jako příklad; coverage má 6. položku „AD approved" (datum v registru), bez ní je podkomponenta beta.
6 · Validator jako zámekpravidla jen v textukaždý A-bug po opravě dostane pravidlo do validate-ds-usage (raw .acrm-icobtn, <kbd> bez .acrm-kbd, N z M v top baru, bespoke *-btn). Co nehlídá stroj, se vrátí.
7 · Tiketypick → oprava → closepick = položka bug listu s číslem D/A; close až po merge opravy a AD potvrzení na webu.

Kapacitní důsledek: rodina = 2–3 dny místo 1 (inventura + matice + AD kolo). Za to výstup, který se nemusí předělávat.

5. Co teď

Běžící dávka Forms & input (Text field · Select · Choice · Date & time) se dokončí podle starého postupu — její výstup projde stejnou review jako Button, než se cokoli prohlásí za hotové; MR dostanou no-auto-merge, pokud ještě nejsou mergnuté.

Rozhodnutí AD potřebná k opravám: A2 (success varianta vs primary+w-full), A3 (framed icon button jako kanon), A6/A7 (osud sm velikostí), A8 (varianty pro auth/toast/empty-state). Vše ostatní (D1–D11, A1, A4, A5, A9) se opraví bez dotazů.

Source: docs/REVIEW-ACRM-76-81-button-family.md · GitLab