Review rodiny Dialog — nálezy, bug list, rozhodnutí pro AD

Datum: 4. 9. 2026 · zdroj pravdy: origin/main acrm (/root/acrm-main, ea77d2cdb) · web: <http://192.168.230.131:3777/ds/components/dialog/dialog?preview=ds-overlays-stories>

0. Shrnutí

Dialog je jedna z nejlépe napsaných komponent v systému: žebřík velikostí, zásobník Esc, stavy okna bez ztráty obsahu, dok minimalizovaných oken, mobilní chování — všechno v jednom souboru a odůvodněné. Vady jsou tři a všechny jsou v přístupnosti a chování, ne ve vzhledu: chybí lapač fokusu, chybí zámek scrollu a plocha pod modálem není pro čtečky vypnutá. Vedle toho existuje jeden ručně postavený modál (GlobalSearch) a formulářový drawer neumí říct, PROČ uložení selhalo.

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

1.1 Dialog (packages/ui/src/components/Dialog.tsx)

  • Velikosti s 400 / m 640 / l fluid (strop 1200). Starší číselné width je označené jako deprecated — a legacy story DialogStory ho pořád používá (width={480}).
  • Fokus: při otevření se přesune do panelu (první [autofocus]/pole, jinak panel). Lapač fokusu ale neexistuje — Tab z panelu odejde do stránky za scrimem.
  • Zámek scrollu neexistuje (document.body.style.overflow se nikde nesahá): stránka pod modálem jde rolovat kolečkem i klávesnicí.
  • Panel se neportáluje (jen lišta doku). Hostitel s transform/filter/contain tedy může posunout fixed panel jinam, než kam patří.
  • Esc řeší sdílený zásobník escStack — správně: vyhraje nejvyšší okno, modeless okno klávesu spolkne, aby nezavřelo drawer pod sebou.

1.2 Confirm (Confirm.tsx)

  • useConfirm() vrací příslib — správný tvar API, 53 volajících souborů.
  • Potvrzovací tlačítko není autofokusované (u destruktivní otázky správně) a nemá klávesovou zkratku.
  • Texty tlačítek dodává hostitel (labels); komponenta nemá fallback — appka, která je zapomene, vykreslí prázdná tlačítka.

1.3 Window states (Dialog.tsx + lib/dialog-dock.ts + ds/dialog-window.css)

  • Kontrakt „přepnutí stavu obsah nezruší" je dodržený i vysvětlený (wrapper se mění na display: contents, min panel jen skryje). Dok je jeden kontejner v <body>, počítaný referencemi, publikuje počet lišt jako --acrm-dialog-dock-n.
  • Žádná story to dosud neukazovala, přestože na to existuje test (apps/web/src/components/__tests__/dialog-window-states.test.tsx).
  • Změna stavu není nijak oznámena (žádná live region), minimalizace fokus zahodí.

1.4 Drawer form (DrawerForm.tsx)

  • Sdílí plášť .pdr s Record drawerem a nepřidává žádné CSS — čistý vztah.
  • error je boolean s jednou natvrdo napsanou českou větou; komponenta neumí říct, co se pokazilo, a chybová věta není live region ani navázaná na pole.
  • Nemá Esc ani lapač fokusu, ale hlásí se jako role="dialog".

1.5 Record drawer (components/drawer/RecordDrawer.tsx, ds/record-drawer.css)

  • Vědomě NENÍ dialog (bez scrimu, bez role="dialog") — správně, výpis vedle zůstává živý.
  • Žádná story neexistovala, přestože je to jeden z nejpoužívanějších plášťů (15 souborů).
  • .pdr-rail/.pdr-sec/.pdr-row patří jen sem; Drawer form je konzument jen vnějšího pláště.

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

#NálezStav
D1Rodina Dialog neměla per-sekci stories (jen tři legacy přehledy)OPRAVENO (5 souborů, 23 stories)
D2Žádná ukázka compact/min/tall/size="l"/subject/headerActions/DrawerForm errorOPRAVENO
D3Chyběly usage.md a a11y.md pro všech pět podkomponentOPRAVENO (10 souborů)
D4Katalog Ladle nenačítal dialog-window.css → stavy okna nešlo ukázatOPRAVENO (provider)
D5Legacy story DialogStory používá deprecated width={480} místo sizeNEOPRAVENO — má vizuální baseline; oprava = změna baseline (rozhodnutí D-A5)
D6Registr popisoval Record drawer jen jako „chrome"; neřekl, že React je v appce a co je čí grammarOPRAVENO (summary + notes)

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

#NálezNávrhKdo rozhodne
A1Modál nemá lapač fokusu (Tab uteče za scrim)doplnit do Dialog.tsx (cyklus Tab uvnitř panelu)AD ano/ne, FE provede
A2Není zámek scrollu — stránka pod modálem rolujezamknout body po dobu modáluAD ano/ne
A3Plocha pod modálem není inert/aria-hiddendoplnit spolu s A1AD ano/ne
A4GlobalSearch.tsx staví .acrm-dialog-scrim + .acrm-dialog ručněbuď rozšířit Dialog o to, co paletě chybí, nebo výjimku zapsat do DS jako vědomouAD
A5Legacy DialogStory na deprecated widthpřepsat na size="m" a přefotit baselineAD (mění baseline)
A6DrawerForm.error neumí zprávuzměnit na `error?: string \boolean`AD ano/ne
A7Panel dialogu se neportálujeportálovat do <body> jako PopoverAD (riziko regrese, samostatný MR)

4. Rozhodnutí pro AD

  • G1 — Dialog, Confirm, Window states, Drawer form, Record drawer jako kanon. Schválit rodinu podle ukázek na webu. Doporučuji ano.
  • G2 — přístupnost modálu (A1+A2+A3 jako jeden balík). Lapač fokusu, zámek scrollu a inert pozadí. Doporučuji ano — jsou to tři vady jednoho typu a opravují se společně; vizuálně se nic nemění, takže to nepotřebuje tvoje oko, jen souhlas.
  • G3 — GlobalSearch (A4). Doporučuji rozšířit Dialog a paletu na něj převést; pokud to vyjde draho, zapsat výjimku do DS, ať to nikdo nekopíruje jako vzor.
  • G4 — DrawerForm.error se zprávou (A6). Doporučuji ano, jinak drawer nikdy neřekne, co se pokazilo.
  • G5 — legacy story na width (A5). Doporučuji přepsat při nejbližší vlně baseline, ne teď — každá změna baseline musí být vidět samostatně.

5. Co teď

  1. MR do acrm polish/ds-overlays-stories (Dialog + Popover dohromady, jedna kategorie) se zámkem no-auto-merge.
  2. DS repo: registr, docs, tenhle review.
  3. Po schválení rodiny: approvedAt, pak G2 jako samostatný MR (chování, ne vzhled).

Source: docs/REVIEW-dialog-family.md · GitLab