# Pravidla projektu — Datamart & CRM
## 0. Jednoduchost především — „containment budget" (META-PRAVIDLO)
**Tohle pravidlo je nad všemi ostatními.** Cílem není mít hodně prvků — cílem je
mít **právě jeden, a ten perfektně vyladěný** (Apple/Attio). Méně rámečků = víc
přehlednosti a prostoru.
**Containment budget — maximálně 1 úroveň rámování.** Rám (okraj / pozadí /
dlaždice) si MUSÍ zasloužit místo jako **skutečná hranice**: krok onboardingu,
input, pravý preview panel, opravdový overlay. Jinak ne.
- **Nikdy rámeček kvůli „uspořádání" seznamu.** Položky seskupuj **prostorem +
hierarchií** (váha / barva / velikost), ne krabičkami. Seznam = ikona + text
oddělený mezerou — **žádný okraj kolem každého řádku**.
- **Ikona je ikona, ne dlaždice.** Žádné pozadí/okraj kolem ikony jen aby
„seděla". Barevná ikona, hotovo.
- **Zákaz „rámeček v rámečku v rámečku".** Když je obsah už uvnitř karty/zóny,
jeho položky jsou ploché.
**Proč to píšu (a proč se to opakovalo):** instinkt „seskup → zabal do boxu;
ikona → na dlaždici" se vrací u každé nové obrazovky. DS dosud popisoval jen
*slovník* (tokeny + jak nakreslit okraj), ne *gramatiku zdrženlivosti* (kdy okraj
NEpřidat). Bez **viditelného anti-patternu** a bez **kontroly před publikací**
ten habit vždycky vyhrál. Tahle sekce + DO/DON'T karta + checklist to mění z prózy
na vynutitelnou bránu.
**Povinná self-review PŘED publikací KAŽDÉ obrazovky:**
1. Spočítej úrovně zanoření (karta → zóna → položka → …). **>1 dekorativní rám =
STOP a zploštit.**
2. U každého okraje/pozadí/dlaždice řekni jednou větou „jakou skutečnou hranici
tvoří". Když to nejde → pryč.
3. „Šlo by to jen prostorem a hierarchií?" Pokud ano → udělej to tak.
Referenční anti-pattern: karta **`Layout · Containment` (DO / DON'T)**. Referenční
flat implementace: **`Registrace · Propojení Google`** + `_ds/auth.css`.
## 0.1 Portuj z reálné appky — NEimprovizuj (META-PRAVIDLO)
**Zdroj pravdy je reálná appka `apps/web/src/`, ne obrázek před tebou.** Než
postavíš nebo přestyluješ JAKÝKOLIV prvek (dialog, picker, ovládací prvek, karta,
seznam, tabulka), **nejdřív prohledej appku**:
- `apps/web/src/components/*.tsx` + balíček `@acrm/ui` (komponenty),
`apps/web/src/ds.css` (sdílené `.acrm-*` třídy) a `apps/web/src/ds/*.css`
(screen vrstvy), tokeny v `packages/ui/src/tokens/tokens.css`. Grepuj podle
názvu funkce (`SaveSegment`, `IconPicker`, `seg-ic`, `dlg-`, `segpick`…).
**Když kanonická implementace existuje → portuj ji 1:1** do bundle: stejné názvy
tříd, stejné velikosti, stejná paleta (**tokeny / tone-třídy, NIKDY natvrdo hex**),
stejná struktura i chování. CSS zkopíruj doslova ze zdrojové vrstvy
(`ds.css` / `ds/*.css`).
**Zakázáno:** paralelní třídní systém (`.ssg-*`, ad-hoc hex, vlastní velikosti) pro
něco, co appka už řeší; natvrdo barvy tam, kde jsou `--ic-*` / pill tóny /
tone-třídy; velikost dlaždice „od oka" (segment tile = 20 px v navigaci/Home,
40 px editovatelný picker trigger v save dialogu).
Teprve když v appce NIC není, navrhuj nově — i tak s DS tokeny a nejbližším
existujícím vzorem. **Krok „prohledej appku jako první" je povinný a jde PŘED
prvním řádkem kódu.** (Doloženo: Save dialog jsem improvizoval místo portu
`save-segment-dialog.tsx` + `segment-icon-picker.tsx` → muselo se zahodit.)
## 1. App Shell je MASTER pro všechny obrazovky (bez výjimky)
**Pravidlo.** App Shell (`_ds/app-shell.js` + `_ds/app-shell.css`, komponenta
`DS.AppShell`) je jediná master šablona aplikačního rámu. **Každá obrazovka,
kterou aplikace obsahuje, se vykresluje skrz `DS.AppShell`** — topbar (logo ·
přepínač Datamart/CRM · hledání · notifikace · účet) a levou navigaci nesmí
žádná obrazovka kreslit sama. Strukturální změna v App Shell se musí promítnout
do všech obrazovek automaticky.
**Jak to dodržet:**
- Obrazovka renderuje `{obsah} `
a do `DS.AppShell` vkládá pouze svůj **obsah** (centrální plocha) a případně
`drawer` (pravý náhledový panel).
- Sdílené obrazovkové enginy jdou taky přes shell:
- `DS.SegmentScreen` (tabulky Lidé/Firmy, Průvodci) vykresluje obsah do
`DS.AppShell` (viz `_ds/segment-screen.js`).
- `DS.PersonDetail` (detail osoby) vykresluje do `DS.AppShell` (viz
`_ds/person-detail.js`).
- Žádné lokální `gbar` / `sidebar` / `pd-gbar` rámy v jednotlivých souborech.
Každá obrazovka musí načíst `_ds/app-shell.css` + `_ds/app-shell.js`.
- Drawery (record / preview) se dokují do slotu `.as-drawer` shellu — elevaci
(stín) určuje **jen** slot, jednotlivé drawery vlastní stín nemají.
**Hranice pojmu „obrazovka".** Obrazovka = navigovatelný pohled aplikace
(Domů, Pipeline, Lidé, Firmy, Company/Deal/Person drawer, Person detail,
Průvodci). Referenční katalogy stavů a komponent (Prázdné stavy, Chybové stavy,
Loading, Notifikace, Toasty) **nejsou** obrazovky — dokumentují stavební prvky
v izolovaných rámech; samotné stavy (loading/empty/error) se v reálných
obrazovkách už dnes zobrazují uvnitř shellu.
> **Výjimka — plnoplošné brány mimo shell.** Registrace / login / onboarding a
> podobné fokusní brány (před vstupem do aplikace) shell NEpoužívají; řídí se
> pravidlem 4 (čisté plochy). Vše ostatní jde přes shell.
## 2. Bonita — barvy odvozuj z ratingu (DS tokeny)
Barva bonity se **odvozuje z písmena ratingu**, nikdy se nezapisuje natvrdo
u dat. Kanonická mapa (zdroj pravdy: `DS.SegData.BONITA_BANDS`):
| Tón (DS token) | Ratingy |
|---|---|
| `green` (`--bonita-green`) | AAA, AA |
| `lime` (`--bonita-lime`) | A, BBB |
| `amber` (`--bonita-amber`) | BB, B |
| `rose` (`--bonita-rose`) | CCC, CC, C |
| `grey` (`--bonita-grey`) | D |
Helper: `DS.gradeTone(grade)` (v `_ds/segment-screen.js`). Vizuál = tlumená
„pill" `.grade ` (světlé pozadí + barevný text z DS tokenů) — stejně
v tabulkách, scale i detailech.
## 3. Avatary — DS styl (hashHue)
Avatary používají DS treatment: světlé pozadí `--surface-75`, 1.5px barevný
okraj a barevný iniciál z `hsl(var(--av-hue) …)`, kde `--av-hue = hashHue(jméno)`.
Kruh pro osoby, zaoblený čtverec pro firmy. Žádné plné syté pozadí s bílým
iniciálem.
## 4. Čisté plochy — bílé pozadí, tenký rámeček, vzdušné (Attio)
Fokusní plochy mimo aplikační shell (přihlášení, registrace, onboarding kroky,
dialogy) musí působit **čistě a vzdušně jako Attio**:
- **Pozadí = bílá** (`--surface-100`). Šedý `--shell-bg` je vyhrazen JEN pro
aplikační rám (topbar + sidebar), nikdy pod fokusní obsah.
- **Každý krok ohranič tenkým jemným rámečkem** — `1px solid var(--border)`,
`--radius-xl`, **velký a vzdušný** (štědrý padding). Přesně jako kroky
onboardingu v Attio. (Tohle Štěpán chtěl — rámeček ano, jen tenký a bez stínu.)
- **Žádný stín — vůbec.** Rámeček = jen tenká linka (`box-shadow: none`).
Drop-shadow (`--shadow-pop`) patří **jen** skutečným **overlayům** (pop-upy,
menu, drawery), nikdy fokusním kartám.
- Vzdušnost přes DS spacing (`--sp-*`): štědré mezery, méně prvků, restraint
(Apple/Attio). Hierarchie váhou + barvou.
- Vnitřní prvky (inputy, řádky zdrojů, preview panely) mají jemný hairline
`--border` nebo tlumené pozadí `--surface-75`.
Referenční implementace: `_ds/auth.css` (`.au-card`) + karty `Registrace · *`.
## 5. Tlačítka — vždy DS tlačítko v jeho definované velikosti
Tlačítka jsou **jediná DS komponenta** (`Components · Buttons`): kanonická třída
`.acrm-btn` v `apps/web/src/ds.css` (`.btn` je alias), v Reactu ji emituje
`@acrm/ui` ``. Žádná obrazovka si nevyrábí vlastní (větší) tlačítka
ani nepředefinovává `.btn` per-screen.
- **Velikost = `--control-h` (≈28–30px), label 12–13px, `--radius-sm`.**
**Nikdy** nevymýšlej „velká" 40–44px tlačítka pro login/marketing. Plnou
šířku řeš `width:100%`, **ne zvětšováním výšky**.
- **Varianty:** `primary` = `--accent` + okraj `--accent-press` (label 500 12px
Inter); `default`/sekundární = bílé + okraj `--line-300` + `--shadow-sm`;
`ghost` = transparentní (hover `--hover-surface`). Google = `default` look + G.
- **Tvar = rounded-square** (`--radius-sm`/`md`); nikdy pill/kruh (ty jsou jen
pro avatary, status-dots a pill-badge).
## 6. Titulky a podtitulky — JEDEN zdroj jsou type tokeny
> **⛔ Verzálky (ALL CAPS) jsou globálně zakázané.** Nikdy nepoužívej uppercase
> „eyebrow"/overline styl sám od sebe — pouze když si o to uživatel **explicitně**
> řekne. Platí pro titulky, labely, tlačítka, menu, názvy sekcí v navigaci
> i mikro-eyebrow. `text-transform: uppercase` se v UI nepoužívá. (Zdroj: DS
> „Psaní titulků a popisů" — „Uppercase overline nepoužíváme".)
**Pravidlo.** Každý titulek a podtitulek obrazovky se odvozuje z **type tokenů**
— nikdy se nezapisuje natvrdo (`font: 600 15px …`). Změna měřítka v jednom místě
(token) se promítne na všechny obrazovky.
> **Zdroj tokenů = kód:** `packages/ui/src/tokens/tokens.css` na `origin/main`.
> `legacy/colors_and_type.css` je export, **ne kánon** — čti ho jen jako historii.
| Role | Token | Použití |
|---|---|---|
| Page title / dashboard hero | `--h1` (Geist 500 16/1.2) | **největší, který shipuje** — pozdrav na Domů, hlavní nadpis stránky |
| Primární titulek | `--h2` (Geist 500 15/1.25) | titulek segmentu, jméno v draweru, titulek sekce, view bar |
| Podnadpis | `--h3` (Geist 500 13/1.35) | menší nadpis uvnitř sekce |
| Eyebrow / overline | `--section` · `--overline` | drobný štítek nad titulkem — **sentence case, NIKDY verzálky** |
| Popisek pole | `--label` (Geist 500 12/22) | popisek nad/vedle vstupu |
| Nápověda pod polem | `--hint` (Geist 400 12/1.45) | vysvětlení, **ne hodnota** — proto 400 (AD 26. 8. 2026) |
| **Podtitulek** | `--subtitle` (Geist 400 13/1.5) | doplňková řádka pod titulkem, **vždy `--fg-secondary`** |
**Rodina je jedna: Geist** (`--font-sans` i `--font-display`), plus `Geist Mono`
výhradně v roli níže. Manrope ani Inter už v systému nejsou — od 29. 8. 2026 (!2571).
`--tracking-tight` je `0`: Geist se záporný tracking nedopisuje.
**Sdílené třídy** (v `colors_and_type.css`): `.ds-page-title`, `.ds-section-title`,
`.ds-subtitle`, `.ds-eyebrow`. Použij je (nebo tokeny) na každém novém titulku.
**Pravidla obsahu (DS „CONTENT FUNDAMENTALS"):**
- **Sentence case** — nikdy Title Case pro titulky/sekce/tlačítka/menu.
- **Bez tečky** na konci titulku (titulek je štítek, ne věta).
- **Max 16px** — hierarchii tvoří **váha + barva, ne velikost**. Žádné 21/26px
display nadpisy uvnitř aplikace.
- **Váhy = 400 / 500 / 600, nic víc** (ruling 6. 7. 2026, karta `Type · Weights`):
600 je strop, váhy 700+ se nepoužívají; native ``/`` je přemapováno
na 600. **Headingy samotné jedou 500** (`--h1`/`--h2`/`--h3`) — 600 zbylo na
`--section` a na zvýraznění uvnitř textu. Hierarchii dělá váha a barva, ne velikost.
- **Nadpis sekce BEZ ikony** (AD 18. 9. 2026, ACRM-359) — `.sb-h` uvnitř detailu
(schůzka, projekt, obchod, osoba…) nese jen text + počet + akce. Ikona před
nadpisem sekce („✓ Rozhodnutí“, „⚠ Námitky“) je dekorace, která se sčítá přes
celou obrazovku; ikony patří řádkům a akcím, hierarchii nadpisů dělá váha a barva.
- **Podtitulky ÚSPORNĚ** — jen když nesou informaci navíc. Žádné dekorativní
„výplňové" podtitulky pod každým titulkem.
- **Eyebrow/overline = sentence case, NIKDY verzálky.** ALL CAPS, kapitálky ani
Title Case nepoužíváme nikde — ani na mikro-eyebrow, ani na názvech sekcí
v navigaci. Hierarchii dělá váha a barva, ne velikost ani velikost písmen.
### 6.1 Monospace — jen finanční výkazy, kód a klávesy
**Rozhodnutí Štěpána 31. 8. 2026 (ACRM-34), potvrzené 2. 9. 2026:** *„Jediné místo,
kde monospace používáme, jsou finanční výkazy."* — „souhlasím s AD, odstraň to mono,
kde to jde."
| | |
|---|---|
| **Mono ANO** | účetní výkazy (kódy účtů, částky; v appce scoped `.acc-page`/`.acc-inv`), doslovný kód, klávesové zkratky (`kbd`), identifikátory ke zkopírování (ID workflow, kódy majetku, QR štítky) |
| **Mono NE** | datové tabulky, meta a termíny v railu, datumy a časy, počty a odznaky, popisky grafů, názvy |
**Kritérium, když je to hraniční:** mono má smysl tam, kde **mřížka glyfů něco
znamená** — sloupec, který se má zarovnat, nebo doslovný kód. Jednociferné číslo
v pilulce nemá s čím lícovat, takže je to dekorace. Sloupce čísel se zarovnávají
přes `font-variant-numeric: tabular-nums` na sans, **ne přes rodinu písma**
(a tabulkové číslice **nikdy u dat a časů** — datum je text, ne číslo; AD 22. 7. 2026).
**Čas, který se nesmí posouvat, bez tabular-nums** (zadavatel 26. 9. 2026, přehrávač
přepisu schůzky — `apps/web/src/components/meetings/TranscriptPlayer.tsx`,
`apps/web/src/ds/meeting-record.css` `.player .tm`): proporcionální text má u každé
číslice jinou šířku, takže odpočet vedle sebe „poskakuje" a stopa přehrávače se s ním
hýbe. Řešení NENÍ tabular-nums (ten je u času zakázaný výše) — buňka dostane pevnou
šířku přes **neviditelné vzory nejširších číslic**: `display: inline-grid`, viditelná
hodnota a dva `aria-hidden` vzory se stejným textem nahrazeným `0` a `8` (nejširší
číslice písma) sdílejí `grid-area: 1 / 1`, takže šířku buňky určuje nejširší z nich a
viditelný text zůstává kanonický proporcionální text, jen s pevnou kotvou.
**Proč se to rozlezlo (a proč to hlídat):** do 29. 8. 2026 mapoval `--font-mono`
na **Inter**, takže monospace fakticky neexistovalo a psát ho kamkoli bylo bez
následku — zažilo se to jako „zvýrazni číslo". `!2571` (Geist) ho přemapoval na
skutečný Geist Mono a tím se mono **tiše obnovilo** na ~100 místech naráz. Není to
sto rozhodnutí, je to jedno rozhodnutí stokrát zopakované. Odklizeno v appce
9 MR (!2770–!2805), v zrcadle !2724.
> **Výjimka — plnoplošné brány mimo shell.** Auth/onboarding (`_ds/auth.css`,
> `.au-title` / `.au-sub`) mají vlastní vzdušnější měřítko — řídí se §4, ne 16px
> capem. Je to **dokumentovaná výjimka**, ne licence pro velké nadpisy v appce.
Referenční anti-pattern (vše už přepojeno na tokeny): lokální `.home-greet` 26px,
`.sgb-title` 14px, `.vtitle` / `.mtitle` / `.pipe-nm` / `.pdr-top-name` 700 15px.
## 7. Navigace — IA žije v App Shell (jeden zdroj)
**Pravidlo.** Informační architektura levé navigace je **jediná konstanta `NAV`**
v `_ds/app-shell.js` (+ `ROUTES`, `PARENT`). Žádná obrazovka nekreslí ani
nedefinuje navigaci sama — vše jde přes `DS.AppShell` (viz §1). Změna IA v
`app-shell.js` se promítne do všech obrazovek automaticky.
- **Typy uzlů:** `row` (řádek nejvyšší úrovně) a `section` (rozbalovací sekce);
v sekci jsou `item` (záznam / segment / průvodce) a `action` („+ nová věc",
přerušovaný okraj). Vzor 1:1 = `templates/sidebar-nav/SidebarNav.dc.html`.
- **Sekce jsou sentence case** (Záznamy · Segmenty · Pohledy) — **NIKDY verzálky**.
Hlavička je klikací (sbalí/rozbalí): chevron vlevo + jemná modrá count kapsle
(`--accent-tint` / `--accent-ink`) vpravo.
- **Badge:** počet na hlavičce sekce = jemná modrá kapsle (`--accent-tint` /
`--accent-ink`, tabular-nums). Notifikační badge na řádku = plný `--accent`,
bílé číslo. Záznamové řádky badge nemají.
- **Klíče `active` jsou kontrakt.** `home · lide · firmy · sro · praha · kraje ·
brno · lide-pruvodce · firmy-pruvodce · pipeline · moje · potermin · riziko` —
při úpravě IA je zachovej, jinak se rozbije zvýraznění aktivní položky i routing.
## 8. Section top bar — uzavřený slovník prvků (jen povolené)
**Pravidlo.** Horní lišta sekce (content toolbar pod App Shellem, na každé
obrazovce) smí obsahovat **POUZE** prvky definované v kartě
**`Components · Section top bar`**. Žádné jiné. Slovník je **uzavřený** — co
tam není, na lištu nepatří.
**Povolené prvky (a nic víc):**
1. **Identita vlevo** — ikona + název sekce. Buď plochá ikona (`--fg-secondary`)
+ název, **přepínač** (`.pipe-btn`: dlaždice/ikona + název + chevron, otevírá
menu), nebo kruhový avatar (`.nav-pav`) u detailu záznamu. **Jen jedna**
identita, vlevo.
2. **Oddělovač `.tdiv`** — svislá linka 1 px / 20 px mezi skupinami ovládání.
3. **Ovládací chip `.tchip`** — rounded-rect (radius-md), v něm: ikona +
volitelně `.k` (tlumený štítek) + `.v` (hodnota, bold) + `.chev` (chevron,
otevírá menu). Tohle je **jediný** prvek pro zobrazení / řazení / filtr /
rozsah. Aktivní stav = modrý okraj (`.tchip.on`), ne výplň.
4. **Výplň `.vspace`** — pružná mezera (`flex:1`), odtlačí akci doprava.
5. **Tlačítko `.btn` / `.btn.primary`** — vpravo; **maximálně jedna** primární
(plná modrá) akce na obrazovku.
**Zakázáno (časté prohřešky):**
- **Pilulky (`.chip`, `border-radius: pill`) jako filtry/taby v liště.** Pill se
v Section top baru nevyskytuje — filtr/přepínač je vždy `.tchip` s chevronem a
rozbalovacím menu (vzor = přepínač zobrazení v kartě).
- **Jiná značka identity, než jakou má táž položka v navigaci.** Rozhodnutí
zadavatele 27. 9. 2026: identita v liště = TATÁŽ značka jako položka v menu
(`nav-catalogue.ts`) — řádek s ikonou → plochá ikona (`--fg-secondary`);
podpoložka s barevnou dlaždicí (`sg`) → barevná dlaždice se stejným glyfem
a tónem. Detail záznamu dědí značku své sekce (projekt = dlaždice Projekty,
úkol = ikona Úkoly, schůzka = ikona Kalendář). Výjimka — přepínač (pipeline): spouštěč ukazuje značku VYBRANÉ položky + chevron, jako každý výběr (zadavatel 27. 9. 2026, 7b).
**Implementace (acrm !4017, vlna H):** značku NIKDY nepiš ručně — ber ji z
`apps/web/src/components/section-mark.ts`: `useSectionMark()` (značka + název aktuální položky
menu), `sectionMarkFor(path, search, mode)`, `sectionMarkForKey(key)` (značka jiné sekce, např.
drobečky detailu úkolu → Projekty), adaptéry `markProps` (→ props `SectionTopBarTitle`) a
`segmentMark` (dynamické segmenty / procesy — značka z dat, shodná s menu). Výjimky, které
helper záměrně neřeší: přepínač pipeline (značka vybrané položky), identita osoby/firmy/obchodu
(`kind`, 32 px), detail úkolu jako drobečky (s projektem = Projekty, bez = Úkoly).
- **Dvě+ primární tlačítka**, sekundární akce naházené do lišty, vlastní
jednorázové „chipy" mimo `.tchip`.
- **Verzálky** v názvu i v chipech (sentence case, §6).
**Escalation (když prvek opravdu chybí).** Slovník nerozšiřuj potichu. Designer
**napíše krátké zdůvodnění Art Directorovi**: (a) jakou potřebu prvek řeší, (b)
proč to nejde stávajícími pěti prvky, (c) návrh tokenů/chování. Teprve po
schválení se prvek **přidá do karty `Section top bar`** (zdroj pravdy) a až pak
na obrazovky. Bez schválení v kartě = na lištu nepatří.
**Self-review před publikací lišty:** projdi prvky zleva doprava — je každý z
povolené pětky? Je identita právě jedna? Je primární tlačítko nanejvýš jedno?
Pokud něco neprojde → nahraď `.tchip`em nebo eskaluj.
Zdroj pravdy + anatomie + DO/DON'T: karta **`Components · Section top bar`**.
## 9. Allow-list — oprávněný keep označ `ds-ok`
Validátor (`pnpm ds:validate`, od 7.7.26 **STRICT** v pre-commitu i CI appky)
shazuje každou natvrdo napsanou hodnotu: px ve spacing/gap/radius místo
`--sp-*` / `--radius-*`, hex místo barevného tokenu / tone-třídy.
**Oprávněný keep existuje** — a MUSÍ nést marker s důvodem, jinak je to porušení:
```css
background: #16b8c4; /* ds-ok: brand tile CRM workspace */
```
```tsx
height: '100vh', // ds-ok: viewport výška, ne spacing token
```
Oprávněné důvody (úplný výčet):
- **brand barvy** (workspace tile; kanálové glyfy ale preferuj přes `--channel-*`),
- **gradient stops** (mapový placeholder, color wheel),
- **true-overlay scrim/stín** definovaný overlay systémem (`--ov-scrim` /
`--overlay-shadow`),
- **layoutové rozměry** (šířka sloupce/panelu, viewport výška — nejsou spacing
škála),
- **auth/onboarding vzdušné měřítko** (dokumentovaná výjimka §4/§6),
- **native control seed** (barva nativního prvku, kterou token neřídí).
Marker patří **na řádek nálezu** (nebo na řádek nad něj). Pozor na prettier:
dlouhé deklarace (gradienty) láme na víc řádků a marker odsune z řádku nálezu —
takové řádky chraň `/* prettier-ignore */`. Marker potlačí TOKEN/COLOR nálezy,
**nikdy HARD** (verzálky výjimku nemají).
## 10. Dialog system — jeden plášť pro všechny overlaye
**Pravidlo.** Každý dialog v aplikaci je instance JEDNOHO systému (karta
**`Components · Dialog system`**, app: `.acrm-dialog` + `` / `useConfirm()`).
Žádné bespoke modaly, žádný `window.confirm`.
- **Scrim** = jednotný závoj `--ov-scrim`; oddělení pláště dělá jen podpisový
stín `--overlay-shadow` (jen skutečné overlaye — nikdy fokusní karty, §4).
- **Tvar** = `--radius-xl`, bílý povrch, obsah clipnutý; max 1 rám (§0).
- **Kotva:** form dokuje ~11vh od horní hrany; **confirm centruje**.
- **Šířky = TŘI TŘÍDY, ne řada čísel** (ACRM-320/321, ``):
**`s` 400** confirm a picker · **`m` 750** formulář (default dialogů se vstupy) ·
**`l` flexi** dokument, e-mail, náhled souboru a buildery — bez pevné míry, šířku bere
z okna se stropem 1200, výšku z obsahu. Číslo v `width` je deprecated: nový dialog si
vybírá příčku, ne pixely. Měřený vzor: Linear 750, Attio 800.
Head+foot pinned, scrolluje jen tělo (`.tall` = 88vh).
- **Patička:** vlevo kontextové ghost afordance (`.lead`), vpravo sekundár a
max jeden primár; destrukce = `.btn.danger` (`--critical`).
- **Klávesy / fokus / motion:** Esc zavře; fokus po otevření na panel
(autofocus input vyhraje) a po zavření zpět na trigger; scrim fade 120 ms,
plášť rise+scale 160 ms, reduced-motion okamžitě.
- **Confirm** (nevratné/destruktivní rozhodnutí): titulek + jedna věta
důsledku + Zrušit + destruktivní primár — v Reactu `useConfirm()` (promise),
nikdy nativní `window.confirm`.
## 11. Přechod mezi stavy prvku — jeden prvek, který se přetváří
**Pravidlo (AD 17. 9. 2026).** Když prvek mění stav (chip nahrávání, tlačítko s průběhem,
lišta, dlaždice), **nevyměňuje se za jiný prvek** — zůstává týž a jen se přetvaruje. Nic
nepřilétá ani neodlétá.
1. **Jeden trvalý prvek.** Rámeček (u chipu kanonický `.tk-chip`) je v DOM po celou dobu; stav
nese atribut (`data-phase`), ne jiná komponenta.
2. **Mění se jen rozměr** — šířka (u svislých prvků výška) z předchozí na novou **kanonickou
PRUŽINOU** (zadavatel 26. 9. 2026, `packages/ui/src/lib/spring.ts`: tlumený oscilátor,
tuhost 190, tlumení 17, ~750 ms, přestřelení ~6 %, WAAPI klíčové snímky). NAHRAZUJE dřívější
`0,29 s cubic-bezier(0.32, 0.72, 0, 1)` (ACRM-152) — ten kánon skončil, nedoplňuje se.
`` (okno) a `MorphText` (šířka textu, `@acrm/ui`) jedou na pružině; oba mají
kanonický záznam v DS registru (`spring`, `morph-text`). Žádná vlastní délka ani křivka.
3. **Obsah se prolne NA MÍSTĚ.** Odcházející vrstva se vyjme z toku (`position: absolute` na téže
pozici, šířku určuje jen příchozí) a rozplyne se (opacity, ~0,14 s ease-out); příchozí najede
opacity 0 → 1 za 0,2 s se zpožděním 0,06 s. **Žádný posun ani zvětšení** — `transform` zůstává
`none`.
4. **Tvar se nemění:** výška, rámeček, radius i písmo jsou ve všech stavech tytéž a obsah je dědí.
Stav „probíhá přepis" nesmí být vyšší než stav klidový — skok rozměru rozbije řadu chipů.
5. **Glyf uvnitř** (pauza ↔ přehrát) se mění prolnutím ikony (opacity + scale 0,6 → 1, 0,2 s), ne
výměnou celého obsahu.
6. **Animuje se jen ZMĚNA STAVU**, ne každý render (tikající časomíra, dotažená data) — jinak se
přechod ruší uprostřed. Změna během běhu začíná z právě viditelného rozměru.
7. **Výchozí rozměr měř v okamžiku akce** (klik), ne z pozorovatele velikosti: ten drží hodnotu
z doby před načtením písma (změřeno 83,7 místo 85 px → poskok zpět).
8. **`prefers-reduced-motion`** = výměna bez animace (`springAnimate` vrátí `null`, odcházející
vrstva `display: none`). Pro celou appku to navíc drží **globální pojistka**
`apps/web/src/ds/reduced-motion.css` (acrm !4008, 27. 9. 2026): poslední vrstva CSS (ve všech
třech seznamech — `main.tsx`, `.ladle/components.tsx`, `ds-proto-build.mjs`), která při
omezeném pohybu zkrátí každý `transition` a `animation` na `0.01ms`, vynuluje delay, pustí
animaci jen jednou a vypne plynulé rolování. **Proč `0.01ms`, ne `0s`:** nulová délka nevyvolá
`transitionend`/`animationend` a kód, který na ně čeká (zavření šuplíku, odmontování), by
uvízl. Pojistka pokrývá i portované soubory, na které se nesahá. Lokální přepis (např. „ruka
zůstane stát“) je přesnější a zůstává. Nový přechod proto nemusí psát vlastní blok, ale nesmí
spoléhat na průběh animace (výsledný stav musí být správný i bez ní).
9. **Ověřuje se po snímcích**, ne okem: rozměr jede po křivce pružiny (viz `spring.ts`), výška
konstantní, `transform: none`, obě vrstvy během prolnutí na téže levé hraně.
**Proč:** výměna prvků čte jako dva různé objekty — oko sleduje „něco přiletělo" místo „tohle se
změnilo". První kolo návrhu nahrávacího chipu takhle vypadalo a AD ho zamítl; přijaté chování je
jeden objekt, který se roztahuje a zužuje.
**Referenční implementace (vše na pružině, acrm !4016, 27. 9. 2026):** ``
(`packages/ui/src/components/Dialog.tsx`), `MorphText` (`packages/ui/src/components/MorphText.tsx`),
`ChipMorph` (`apps/web/src/components/notes/ChipMorph.tsx` — šířka chipu; sdílí ho i `NoteRecChip`
v `NoteAudio.tsx`), kurzorový pruh výpisu `.il-cursor-bar` (`ItemList.tsx`) a formless okno:
`use-avail-spring.ts` (šířka okna + posun, posun drží, dokud šířka nedojede — `SPRING_ARRIVE_MS`)
a `use-formless-height-spring.ts` (výška `.fl-body`, obsah se nehýbe) v
`apps/web/src/components/meetings/`. Dřívější `@keyframes flaSettle` a CSS přechody šířky/výšky
formless okna zanikly. Stará křivka `cubic-bezier(0.32, 0.72, 0, 1)` smí zůstat jen u
**prolnutí obsahu** (bod 3), vstupních animací a barvy — nikdy u změny rozměru.
**Past — vlastní proměnná čtená z JS:** proměnnou s `clamp()` / `100vw` (např. `--fla-shift`)
Chromium v `getComputedStyle(el).getPropertyValue()` nevyhodnotí — vrátí nevyhodnocený výraz,
ne pixely. Když JS potřebuje hodnotu v px (cíl pružiny), čte ji přes skrytou sondu (prvek
s `width: var(--x)` a měří jeho `getBoundingClientRect().width`), ne ze stylu proměnné.
## 12. Žádné dvě úrovně záložek nad sebou
**Pravidlo (zadavatel 27. 9. 2026 zamítl TabStrip pod taby v detailu uživatele).** `TabStrip` /
`Tabs` řádek se **nikdy** nekreslí pod jiným taby řádkem — jedna obrazovka nese nejvýš jednu
vodorovnou řadu záložek. Když obsah potřebuje druhou úroveň volby:
- **Je to nová stránka, ne záložka.** Druhá úroveň navigace = vlastní položky menu + vlastní
stránky (vzor `Settings page`, karta `Section top bar · Settings page`) — ne vnořený TabStrip
pod existujícím `record-tabs`. Osobní nastavení a preference **nejsou záložka detailu
záznamu** (uživatele, firmy, …); patří do Nastavení jako vlastní položka a vlastní stránka.
- **Nebo je to jeden `Segmented control` uvnitř stránky** (např. E-mailové šablony „Moje |
Firemní"), ne druhý řádek tabů. Segmented je přepínač obsahu JEDNÉ stránky, ne navigace mezi
stránkami — nesmí se řadit pod `.tbar`/`record-tabs` jako by to byl další tab strip.
**Zakázáno:** `TabStrip`/`Tabs` bezprostředně pod `record-tabs` (nebo pod jiným `TabStrip`), a to
i když je vizuálně odlišený (menší, jinak podbarvený) — dvě úrovně tabů nad sebou zamítá i
vizuálně upravená varianta. Řešení je vždy buď samostatná stránka (Settings pattern), nebo
`Segmented control` v obsahu, nikdy druhý tab řádek.
Zdroj pravdy + anatomie: karty **`Components · Tab strip`**, **`Components · Record tabs`** a
**`Components · Settings page`** (`docs/section-top-bar/settings-page`).
## 13. Rozměr = token podle VÝZNAMU
**Pravidlo (zadavatel 27. 9. 2026, vlny F + F4).** Rozměr v CSS se nepíše číslem, ale tokenem,
který říká, **co to je** — ne jakou má hodnotu. Stejné číslo s jiným významem = jiný token
(`--pill-h` a `--tile-sm` mají obě 22 px, ale můžou se rozejít). Zdroj: `packages/ui/src/tokens/tokens.css`.
| Co to je | Token | Hodnota |
| --- | --- | --- |
| glyf / ikona | `--ico-2xs` · `-xs` · `-sm` · `-md` · `-lg` · `-xl` | 12 · 14 · 16 · 18 · 20 · 24 |
| mezera, odsazení | `--sp-*` | škála mezer |
| výška ovládání (tlačítko, přepínač) | `--control-h` | 30 |
| kompaktní ovládání (Button compact, IconButton sm, tlačítko v řádku) | `--control-h-sm` | 24 |
| vstupní pole, select, hledání + ovládání topbaru, které s nimi lícuje | `--field-h` | 32 |
| řádek výpisu / navigace | `--row-h` · `--row-h-nav` | 37 · 28 |
| avatar v textu · výchozí · velký | `--avatar-xs` · `-sm` · `-lg` | 16 · 24 · 32 |
| tečka indikátoru · tečka stavu | `--dot-sm` · `--dot` | 6 · 8 |
| pilulka malá · pilulka | `--pill-h-sm` · `--pill-h` | 20 · 22 |
| dlaždice přepínače (`.pipe-tile`) | `--tile-sm` | 22 |
| silná čára (indikátor záložky, spoje) | `--line-thick` | 2 |
1. **Nový token jen s významem**, ne pro jedno číslo. Když hodnota nemá význam na škále
(komponentní rozměr, kresba 1:1 z předlohy), zůstává **viditelným dluhem** v ratchetu — ne
`ds-ok` jen kvůli čistému výpisu. `ds-ok` patří tam, kde by byl nutný i v novém kódu (glyf =
výška sousedního textu, iniciály avataru) a vždy s důvodem na řádku.
2. **Baseline ratchetu (`dluh`, `suppressed`) se nikdy ručně nezvyšuje** — mr-bot to hlásí jako
obcházení brány. Ratchet jen klesá.
3. **Glyf v boxu má paritu s boxem**: box − glyf = sudé číslo (18 → 12 / 14, 32 → 14 / 16);
lichý rozdíl = kresba na půlpixelu, vypadá rozmazaně.
4. **Tokenizace nemění hodnotu.** Změna hodnoty (např. pole 32 → 30) je vizuální změna → návrh.
## 14. Žádné popisné texty v základním UI — vysvětlení do (i)
Zadavatel 3. 10. 2026: „přesně takový přístup musíme mít, žádné dlouhé popisné texty v základním UI, vše schovávat do (i)“ (návrh v2 na Nastavení, `:3777/preview/vysvetlivky-info-tooltip.html`). Navazuje na AD 9. 8. 2026: nikdo je nečte.
1. **Nejdřív škrtat.** Věta, která nemění rozhodnutí uživatele, zmizí. Význam nese struktura, hierarchie, glyf a chování.
2. **Co zbyde a mění rozhodnutí** (důsledek, nevratnost, odkud se berou data) jde do **InfoTip (i)** za nadpisem sekce nebo popiskem pole: klik / tap otevře, tap mimo nebo Esc zavře, max 2 věty / 280 px, na dotyku terč `--tap-min`. Tooltip zůstává jen pro název akce nebo důvod, ne pro vysvětlení.
3. **Viditelné zůstávají:** stav („Na účtu zatím není žádné číslo“), chyba, varování, které se musí přečíst DŘÍV (např. automatizace odešle reálné e-maily), data a jediný návod k akci.
4. **Příklad nebo formát hodnoty** („např. věcně, zdvořile…“) patří do placeholderu pole, ne pod pole.
5. Sekce, po které zbyde jen nadpis + (i), se přeskládá. Sekce bez obsahu nestojí sama.
6. Doplňkový světlý text (`--fg-muted`) zůstává světlý (zadavatel 3. 10. 2026); co má být čitelné, je `--fg-secondary`.
7. **Hostitel (i)** je nejbližší nadpis nebo popisek: `SectionBlock info`, `Field labelExtra` (stojí VEDLE `` v `.acrm-field-head`), `.flabel`, `.dlg-label`, `SegCol.info` (hlavička sloupce tabulky). (i) nikdy uvnitř `` obalujícího ovládací prvek a nikdy v tlačítku; vysvětlení přepínače nebo zaškrtávátka jde do (i) jeho pole. Kde hostitel chybí (prázdný stav), zůstane jedna viditelná věta.
8. Krátké varování před nevratnou akcí stojí u tlačítka („Odejde přesně tento text.“), ne v (i).
**Výsledek akce 3. 10. 2026** (10 MR: !4504, !4507, !4511, !4515, !4517, !4519, !4520, !4522, !4525, !4529): ~130 textů do (i), ~40 smazáno, 11 do placeholderu, ~85 zůstalo viditelných (stavy, chyby, varování, jediné návody). Po cestě opraveno: popisek pole otevíral (i) (labelExtra uvnitř ``), Avatar bez fallbacku na iniciály, přetékání Průvodce na 390, potomkovský selektor `.sgb-flabel .ti`.
## 15. Upozornění = červená a původní ikona
Zadavatel 3. 10. 2026 (zamítl amber u duplicit: „fuj, hnědá, škaredej trojúhelník… pokud to někde takto používáme, změň to všude“):
1. Upozornění (ikona + text u řádku, v dialogu, u duplicity, varování v okamžiku rozhodnutí) má glyf `alert-square-rounded` a barvu `--critical` (glyf) / `--critical-ink` (text).
2. `alert-triangle` a text v `--warning-ink` se u upozornění nepoužívají.
3. Datové barvy v grafech a ukazatelích (pásmo Koučinku `--ic-amber`, reporty, stat tile) nejsou upozornění a zůstávají.
4. **Hnědá se v aplikaci nepoužívá vůbec, nikdy** (zadavatel 3. 10. 2026: „vůbec nepoužívej hnědou… nikdy“). Hnědé jsou tokeny `--warning-ink` a `--pill-amber-ink` ve světlém režimu (#a35200) a `--warning-subtle` / `--pill-amber-bg` v tmavém (#331b00). Žádný text, glyf ani plocha v hnědém tónu. Jasná oranžová (`--ic-amber` #ff990a, `--warning` #ffb224) hnědá není.
5. Upozornění a chyba vypadají stejně (glyf `alert-square-rounded`, `--critical`) — i u toastu (`ToastProvider`: warning a error). Rozliší je text, ne barva ani jiný glyf. Návrh „varování jinou barvou / jantarovou“ (i z review bota) se zamítá s odkazem na tento bod (AD 4. 10. 2026, acrm !4576).
6. **Barva nese prioritu, ne kategorii** (zadavatel 4. 10. 2026 k souhrnu importu: „vše červené, není vidět priorita… vrcholem je červená ikona na zeleném pozadí“). Červená jen u toho, co vyžaduje rozhodnutí hned (a jen glyf + titulek / počet); kontrola = neutrální nebo accent; hotovo / informace = tichý řádek bez rámečku. Glyf má vždy tón svého řádku / pruhu — nikdy červený glyf na zeleném nebo jiném tónu. Bloky na jedné stránce mají jednu šířku. Pořadí shora: rozhodni → zkontroluj → hotovo.
## 16. Názvy tříd bez segmentu „ad“ (blokátory reklam)
Incident 6. 10. 2026 (ATSL, FA256): blokátor reklam schoval popisek `.cwk-adlabel` v liště „celý den“ týdne kalendáře, grid poskočil o sloupec a celodenní schůzky visely o den dřív. Kosmetické filtry (EasyList, AdGuard, uBlock) skrývají prvky podle tříd a id s „ad“ segmentem.
1. Žádný segment třídy ani id (část mezi `-` / `_`) v kánonu, kartách, screens ani v appce nesmí být `ad`, `ads`, `adv`, `advert…`, `sponsor…` ani `ad` + slovo (`adlabel`, `adcell`, `adev`, `adbox`, `adbanner`, `adslot`…). Běžná slova (`add`, `addr`, `admin`, `adresa`) jsou v pořádku.
2. Zkratky piš celým slovem: celodenní = `allday` (`.cwk-allday-label/-cell/-ev/-more/-pop`), nikdy `ad`.
3. Appku hlídá kontraktní test `apps/web/src/ds/__tests__/tridy-bez-reklamy.contract.test.ts` (acrm !4780); název ze starého kánonu by při portu neprošel.
## 17. Myš a dotyk mají přednost před klávesnicí; přetahování vždy s kopií pod kurzorem
Zadavatel 6. 10. 2026 (FA255, přes CTO: rozbité přetahování obchodů po refaktoru desky z 3. 10. — tažená karta „se nechytne myši", jen ztlumení a čára):
1. **Vzhled, chování a funkční prvky UI mají přednost před přístupností z klávesnice.** Klávesové ovládání se přidává jen tam, kde nic nezhorší pro myš a dotyk; kvůli němu nesmí z toho, co uživatel vidí a používá, nic ubýt. Návrh, který kvůli klávesnici ubírá viditelné chování, se zamítá s odkazem na tento bod.
2. **Každé přetahování v aplikaci má kopii taženého prvku, která jede s kurzorem / prstem** — `.acrm-drag-ghost` (`apps/web/src/ds/reorder.css`: podklad `--bg-canvas`, stín `--shadow-pop`, `--radius-md`) přes sdílené `liftDragGhost` / `useReorder` z `@acrm/ui` (acrm !4774, !4776). Místo dopadu ukazuje rozestup cíle a zvýrazněné místo `.acrm-drop-slot` (čárkovaný `--accent`, podklad `--accent-tint`, pod kopií) přes `dropGap` z `@acrm/ui` (zadavatel 6. 10. 2026, acrm !4796 — čára „sem“ nestačila a zanikla). Ztlumení zdroje a místo dopadu jsou doplněk, kopii nenahrazují. Z návrhů ji neodstraňuj.
## 18. Tlumený text `--fg-muted` se nemění
Zadavatel 7. 10. 2026 (R15, podnět FA257 z Datamart e2e): `--fg-muted` (`--ink-400`, světle #98999c, tmavě #717479) zůstává, i když jako běžný text nesplňuje WCAG AA (2,85 : 1 / 3,63 : 1). Je to vědomé rozhodnutí o vzhledu (RULES §17.1 — vzhled má přednost). Návrh ztmavit token nebo zavést druhý „textový" tlumený token se zamítá s odkazem na tento bod; kde text musí být čitelný naplno, patří do `--fg-secondary` / `--fg` podle významu.
## 19. Dotykový cíl na telefonu ≥ 44 px, vzhled se nemění
Zadavatel 8. 10. 2026 (bod 16, FA257, acrm !4882, prototyp `proto/touch-targets-mobile@522540199`):
1. Pod 720 px má každé `.acrm-icobtn` (i `sm` a `framed`) a `.acrm-btn` neviditelnou plochu `::after` aspoň 44 × 44 px (`apps/web/src/ds/responsive.css`, selektor v `:where`). Glyf, rozměr tlačítka ani hustota řádků se nemění; desktop beze změny. FAB je vyňatý.
2. Plocha nesmí krást klepnutí sousedovi: roste od levého horního rohu doprava a dolů, v liště sekce (`.tbar`) svisle na střed; řádky výpisů (`.il-li`) a pole formulářů se kreslí nad ní; `⋮` sloupce tabulky má plochu jen do výšky. Ověřeno 18 tras × 430 klikatelných prvků, 0 % cizí plochy.
3. Nové tlačítko vlastní `::after`, `min-height` ani „velkou" variantu pro telefon nedostává — cíl řeší toto pravidlo. Kde předek plochu ořízne (`overflow`), je to chyba k opravě, ne důvod ke zvětšení vzhledu.
## 20. Štítky výpisu vždy ve slotu `tags`
Zadavatel 8. 10. 2026 (zadání komunikace na kartě R13, acrm !4915, review AD FA258 / FA259 / FA264):
1. **Pilulky, které řádek výpisu štítkují** (štítek pošty, tag úkolu, štítek projektu), patří do slotu `tags` komponenty `ItemList` — nikdy do `cols`, `meta` ani do vlastního sloupce. Slot kreslí pilulky doprava těsně před datum, bez pevné šířky; nepřeroste polovinu řádku, název má přednost. Stavová pilulka ve sloupci tabulky štítek NENÍ a zůstává.
2. Výpis se štítky nastaví na obalu `--il-meta-w` (jedna šířka data pro všechny řádky), aby pravá hrana štítků lícovala.
3. Na telefonu (hustota `lg`) komponenta sama zalomí štítky na vlastní linku pod název i datum přes celou šířku; volající nic nevětví. `lg` má `padding-block: var(--sp-3)`.
4. Hlavička skupiny `.tl-grp` nad vnořeným výpisem (`width="embedded"`) nese `.is-embedded` a jde přes celou šířku boxu; žádné lokální dorovnání v obrazovce.
5. Pilulka s **vedoucí ikonou** nemá tečku a ikona nese tón (`.acrm-pill > .ti { color: var(--pill-dot) }`) — obecné pravidlo pilulky, ne výjimka jedné komponenty. Štítek projektu (Project label) je `.acrm-pill sm` + ikona projektu, bez vlastního CSS.
6. Platí pro **všechny** výpisy (pošta, komunikace, úkoly, `TaskList` v `@acrm/ui`); nový výpis se štítky bez slotu `tags` se nevydává.
## 21. Přiřazení k záznamu = rovnou našeptávač, nikdy dialog s tlačítkem „Vybrat…"
Zadavatel 13. 10. 2026 (dialog „Přiřadit ke kontaktu" s vnořeným „+ Vybrat kontakt"): „tenhle úplně debilní dialog, který místo toho, aby rovnou člověk vybral osobu, má v sobě vnořený další dialog, musíš předělat, nikdy nikde už ho nechci vidět, u prolinkování osob ani firem … musí to fungovat stejně jako když přidávám nový obchod — rovnou se otevře našeptávač institucí nebo lidí nebo obojího dle toho, co se přidává — musí to fungovat všude."
1. Akce „Přiřadit / Propojit / Vybrat osobu · firmu · obojí" otevře **rovnou velkou paletu hledání** jako u Nového obchodu (`DealSubjectStep`: šířka `paletteWidth` = ⌘K, `PaletteSkeleton`, sekce, patička ↑↓), s kurzorem v hledání; na telefonu šuplík na plnou výšku. **Ne malý našeptávač v Popoveru u spouštěče** — zadavatel 14. 10. 2026: „vybírám si v pohodlném UI, s kým chci obchod otevřít; v malém inline dialogu se blbě hledá, když mám třeba časté jméno". Typ výsledků podle toho, co se přiřazuje: lidé, instituce, nebo obojí.
2. **Zakázáno**: dialog, jehož jediný obsah je spouštěč výběru („+ Vybrat kontakt" / „Vybrat firmu…") a tlačítko „Přiřadit". Výběr položky v našeptávači je potvrzení — žádný druhý krok.
3. Dialog smí obalit výběr jen tehdy, když vedle něj sbírá další údaj (např. typ vztahu, role); i pak je našeptávač pole uvnitř dialogu otevřené hned, ne vnořený dialog.
4. Vzor: výběr firmy/osoby u Nového obchodu — paleta je tam záměrně (zadavatel 14. 10. 2026). Malý `RecordSelect` u spouštěče zůstává jen pro výběr hodnoty z krátkého seznamu (stav, typ), ne pro hledání záznamu.
## 22. Nic navíc proti prototypu a zadání
Zadavatel 13. 10. 2026 (bubliny chatu): „tam žádný zelený proužek v návrhu nebyl … vyhalucinoval jsi ho a ještě jsi domrvil tvar bubliny."
1. Implementace i návrh obsahují **jen** prvky ze schváleného prototypu / zadání nebo z kánonu. Nový prvek (proužek, ikona, štítek, text, barva, tvar) se nepřidává „pro jistotu" — buď ho zadavatel viděl a schválil, nebo tam není.
2. Změna tvaru nebo vzhledu existující komponenty, o které zadání nemluví, je chyba, ne vylepšení.
3. Kontrola před odevzdáním: snímek DNES × PO × prototyp a seznam rozdílů; každý rozdíl buď vysvětlený zadáním, nebo odstraněný.
## 23. Dlouhý text se zkracuje „…", celý na hover / klik
Zadavatel 13. 10. 2026: „proč přestalo fungovat zkracování dlouhého textu na …? Teď to celé rozbíjí UI … musí to fungovat tak, jak to fungovalo, a dlouhý text zobrazit po hover nebo klik."
1. Titulek, jméno, předmět a buňka ve výpisu, kartě, liště i pilulce mají jeden řádek s `text-overflow: ellipsis` (`min-width: 0` na flex/grid potomkovi). Dlouhý text nikdy nerozšíří řádek, sloupec ani dialog.
2. Celé znění: `title` / Tooltip na hover, na dotyku klik (detail nebo popover). Víceřádkový clamp jen tam, kde to kánon komponenty říká (popis v railu, náhled zprávy `previewLines`).
3. Regrese zkracování je chyba s nejvyšší prioritou — vizuální testy mají mít aspoň jeden řádek s extrémně dlouhým textem v každé rodině výpisů.
## 24. Méně textu v UI — rozšíření §14
Zadavatel 13. 10. 2026: „nebuď tak ukecaný v UI!!!! Zákaz si to vypisovat všude viditelně hromadu textu, NIKDO TO NECHCE ČÍST!!!!"
1. Viditelný text v UI = popisky, hodnoty, akce. Vysvětlení, podklady, zdůvodnění, „co se stane", nápověda a AI odůvodnění patří do (i), do sbaleného „Podklady" nebo do detailu — nikdy jako odstavec v základním zobrazení.
2. Měřítko: v řádku, kartě i dialogu nanejvýš jedna věta stavu; cokoli delšího je chyba podle §14/§24.
## 25. Jedna věc = jeden ovladač, jedna cesta
Zadavatel 13. 10. 2026 (podpis v psaní e-mailu: ikona Podpis vedle „Podpis: plný ▾"): „Nemůžou být 2 stejné věci vedle sebe 2 různými cestami." V acrm `docs/UI-RULES.md` §2.2 (!5017), oprava !5019.
1. Vedle sebe nikdy dva ovladače téže věci. Novou schopnost (varianta, volba) přidej do nabídky existujícího prvku.
2. Zdvojení se neřeší odlišením vzhledu (sebrat ikonu, přebarvit); jeden z ovladačů zmizí.
3. Vzor: psaní e-mailu má pro podpis jen ikonu `signature` → Picker menu: osobní varianty (druhý řádek = náhled textu podpisu, je to hodnota, ne vysvětlení) — oddělovač — firemní podpisy — oddělovač — × Bez podpisu; ✓ u podpisu, který leží v těle. Na telefonu ⋯ → Podpis = spodní šuplík s „Hotovo".
4. Plovoucí ovladač (FAB „+", plovoucí přepínač rozložení) obsah překrývá záměrně — to není zdvojení ani chyba překryvu (zadavatel 14. 10. 2026).
5. **Dvě ikony s téměř stejnou nabídkou = jedna ikona.** Vzor: „Později" (LaterMenu, AD 24. 10. 2026, a2a FA269) nahrazuje dvojici Odložit + Připomenout — jedna ikona v liště čtečky pošty, v nabídce přepínač „Připomenout" jako řádek Picker menu. Čip odloženého stavu je `Button` ghost s tónem, ne vlastní třída.
3. **Ikona a ovladač před textem.** Kde stačí ikona nebo ovládací prvek (šipka posunu, ⋮, chevron, čip), nepíše se text. Zadavatel 14. 10. 2026 (záložky detailu firmy, „+ 8 další"): „preferuj ikony a jine ovladaci prvky misto textu". Funkce se ale neztrácí: „+ N další" otevírá vypnuté záložky, takže ji nahradí ikona, ne nic (návrh 24g). Ikonový ovladač má vždy `aria-label` a tooltip.
## 26. Viditelnost akce podle frekvence a kontextu
Zadavatel 14. 10. 2026 (prototyp jednotné hlavičky s modrým „Nový obchod" u osoby): „Vůbec takhle nechci … to tlačítko chci jenom někdy, když to má opravdu význam. U osoby nebo kontaktu nezakládám každý den obchod … Chci, aby ten systém byl pěkný a kontextový … obchod na tom místě, kde nad ním přemýšlím … komunikace je velmi častá, ale obchody jsou zřídkavé (u 200 000 kontaktů 20 obchodů)."
1. Viditelná akce v hlavičce / liště (a zvlášť modrá primární) jen pro činnost, kterou uživatel v tom kontextu dělá opakovaně (Komunikace → Napsat; výpis úkolů → Nový úkol).
2. Vzácná akce patří do kontextu, kde o ní uživatel přemýšlí — sekce nebo záložka té věci (Nový obchod v „Nejbližší aktivitě" vedle „… nemá otevřený obchod", v sekci / záložce Obchody osoby, v procesu), případně do ⋮. Nikdy trvale svítící v hlavičce. Zadavatel 14. 10. 2026: „zmizelo to CTA v Nejbližší aktivita!" — kontextové CTA se neodstraňuje.
3. Akce, která už nemá smysl (obchod existuje), se nenabízí výrazně.
4. „Jedna primární akce" (UX základy bod 4) je strop, ne povinnost: hlavička detailu záznamu primár mít nemusí.
## 22. Stav patří do řádku štítků jako pilulka
Zadavatel 15. 10. 2026 (prototyp čtení e-mailu `proto/mail-cteni-kontext`, ACRM-523 / ACRM-525): „udělej to jako štítek … nemělo by to být výjimka, zaveď to jako pravidlo". Nahrazuje dřívější výklad z review !4390 („pilulka = jen kategorie, stav tečkou s textem").
1. **Stav záznamu nebo vlákna** (Čeká na vaši odpověď, Čeká na ně, Po termínu, Delegováno…) se kreslí jako pilulka `.acrm-pill sm ` **ve stejném řádku jako štítky** (obchod, štítek pošty, příloha) — všechny „štítky" jedné věci drží pohromadě, nikdy rozházené do hlavičky, meta řádku a těla.
2. Stavová pilulka **nemá křížek** (stav se mění akcí, ne odebráním) a nese **významový tón** (`amber` čeká, `rose` po termínu, `green` vyřízeno, `grey` neutrální); štítek pošty má barvu štítku. Tím se stav a kategorie liší, aniž by stav musel být jiný prvek.
3. Ve výpisu jde stavová pilulka do slotu `tags` (§20) před štítky; ve sloupci tabulky zůstává stavová pilulka sloupce.
4. Tečka + text (`.il-dot`) zůstává jen tam, kde pilulky nejsou (hustý řádek bez slotu `tags`, legenda grafu).
## 27. Prázdný stav stojí uprostřed plochy — i svisle
Zadavatel 21. 10. 2026 (nepropojené kanály Komunikace, `proto/kanaly-nepropojene`): „vertikálně vystřeď na stránce — zapiš to do DS — všechny prázdné stavy vystřeď".
1. Prázdný stav se kreslí **uprostřed plochy, kterou nahrazuje — vodorovně i svisle**. Platí pro `EmptyState` (`.acrm-empty`, tón prázdno i chyba), „nic nenalezeno" i nepropojený / nenastavený modul (např. `ChannelConnectPane`). Plocha = to, co by jinak zaplnil obsah: stránka pod lištou, výpis pod FilterBarem, panel, záložka.
2. Hostitel vyplní zbytek plochy (`flex: 1; min-height: 0` ve sloupcovém flexu) a stav v něm stojí na středu (`margin: auto` nebo `justify-content: center`). Lišta, záložky a FilterBar zůstávají nahoře — vystředí se jen obsah pod nimi.
3. Je-li obsah vyšší než plocha (telefon), začíná nahoře a plocha roluje; `margin: auto` se sám srazí na 0, nic se neořízne. Žádná výjimka „na mobilu nahoru".
4. Sekce, jejíž výška se řídí obsahem (sekce railu, karta, popover), nemá co středit svisle — tam jen vodorovně a varianta `.sm`.