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 naorigin/mainacrm (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 storiescomponents--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 kbdna.acrm-kbd.
ACRM-77 · Save button — „příklad kompletně špatně; proč je to zvlášť"
- Fakt:
.dlr-saveje raw<button className="dlr-save">vDialerPage.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), nebovariant="primary"+w-full. Poté.dlr-savesmazat.
ACRM-78 · Icon button — velikosti: „není vidět rozdíl, vždy stejná ikona"
- Fakt:
size="sm"= 24 px vs default--control-h30 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.sm2 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-icobtnv 15 souborech vs<IconButton>v 19. - Verdikt: Sizes = stejná ikona, obě velikosti vedle sebe s px, bez
iconSize; rozhodnout osudsm(smazat, nebo dát reálné použití);.pr-iconbtn→IconButton variant="framed"; raw.acrm-icobtnmigrovat 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
SectionTopBars „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ý slotvspacepř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
.splitv legacybuttons.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)
| # | Kde | Chyba | Oprava | Rozhoduje |
|---|---|---|---|---|
| D1 | registr | kbd-hint, save-button, split-button vedené jako podkomponenty | odstranit; kbd → sekce Buttonu + Text field; save → varianta Buttonu (viz A2); split → nic | AD jen A2 |
| D2 | web | Examples: „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 | — |
| D3 | web | fallback „Legacy mockup" v Examples (celý screen jako příklad) | odstranit; legacy jen jako odkaz v hlavičce | — |
| D4 | web | coverage ✓ stories i pro vypůjčenou story (components--buttons u Kbd hint) | story se počítá jen když její title odpovídá podkomponentě | — |
| D5 | web | iframe příkladu pevných 260 px → bílé plochy | auto-výška z contentDocument (katalog je same-origin) + min 80 px | — |
| D6 | web | „Sizes" u Icon buttonu zobrazuje iconSize jako velikost | Sizes = jen rozměr kontroly; glyf do Code/props | — |
| D7 | stories | Icon button Sizes: tři různé ikony | stejná ikona (dots-vertical) v obou velikostech, popisek s px | — |
| D8 | stories | Icon button States: disabled = rest | po A1: rest · hover · open · disabled; story nesmí ukazovat stav, který CSS nemá | — |
| D9 | stories | Icon button Composition: „11 of 36" + primár za kebabem | 1:1 hlavička záznamu (identita · Spacer · ReservationControl · kebab) | — |
| D10 | stories | Button Default = legacy components--buttons + nová Default vedle sebe | legacy story smazat z registru (baseline přesunout) | — |
| D11 | docs | usage/a11y pro kbd-hint, save-button, split-button | smazat / sloučit do button.usage.md | — |
| D12 | proces | pilot 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)
| # | Kde | Chyba | Oprava | Rozhoduje |
|---|---|---|---|---|
| A1 | ds.css .acrm-icobtn | chybí :disabled, [aria-expanded=true]/open, :active | doplnit stavy (disabled: --fg-muted, not-allowed, bez hover; open: --active-surface) | — |
| A2 | DialerPage.tsx:1587 + dialer.css:672 | bespoke .dlr-save (zelený full-width) mimo Button | Button variant="success" (nová varianta, jediné povolené použití = completion rychlostního formuláře) nebo primary + w-full; .dlr-save smazat | AD |
| A3 | person-record.css:149 .pr-iconbtn (9 souborů) | druhý, orámovaný ikonový button mimo balíček | IconButton variant="framed" (32×30, border-strong, shadow-xs) a migrace 9 souborů; .pr-iconbtn smazat | AD (potvrdit, že framed je kanon) |
| A4 | 15 souborů raw <button className="acrm-icobtn"> | obchází komponentu (žádný type=button, různé aria) | migrovat na <IconButton>; validator: pravidlo icobtn-raw (hard) | — |
| A5 | app-shell.css:170 .as-search kbd | druhý styl kbd | nahradit .acrm-kbd; pravidlo validatoru na <kbd> bez třídy | — |
| A6 | IconButton size="sm" | 0 použití přes komponentu, 2 raw | smazat 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 smazat | AD |
| A8 | .au-btn (auth, 8 souborů), .est-btn (empty/error state), .rvsw-btn, .toast-btn, .sk-btn | 5 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 vedle | AD u nových variant |
| A9 | SectionTopBar | pravidla („žádný count", pořadí slotů) jsou jen v komentáři zdrojáku | př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):
- 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?".
- Story jsem bral jako důkaz existence. Story „disabled" prošla buildem i baseline, ale ukazovala neexistující stav. Baseline zachytí regresi, ne nesmysl.
- 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.
- 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.
- 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):
| Krok | Dnes | Nově |
|---|---|---|
| 1 · Inventura před registrem | název + třída | Tabulka 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ávrh | volná kompozice | Composition = 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 merge | mr-bot merge | Stories 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že | coverage 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ámek | pravidla jen v textu | kaž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 · Tikety | pick → oprava → close | pick = 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ů.