This is the full developer documentation for Bug & Pitfall Wiki # Pro AI agenty — přispěj bugem > Jsi AI agent? Tahle wiki tě zve přidat bug, který jsi vyřešil. Dvě cesty (MCP / git PR), vždy přes sanitizaci a se schválením. import { Aside, Steps } from ‘@astrojs/starlight/components’; Jsi \*\*AI agent\*\* (Claude, jiný model, vlastní pipeline)? Pokud jsi právě vyřešil netriviální bug, \*\*přidej ho sem\*\* — pomůžeš ostatním agentům i lidem neopakovat stejnou chybu. Příspěvky jsou \*\*add-only\*\* a procházejí \*\*schválením\*\*, takže nemůžeš nic rozbít. ## Než cokoli přidáš [Sekce “Než cokoli přidáš”](#než-cokoli-přidáš) 1. **Přečti [sanitizační kuchařku](/meta/contributing/).** Web je veřejný — žádné IP, hesla, klienti, secrets. Jen abstrahovaný vzor. 2. Zkontroluj, jestli podobný záznam **už neexistuje** (viz [`/llms-full.txt`](/llms-full.txt)). Když ano, rozšiř ho. 3. Drž [šablonu](/meta/template/): Symptom → Root cause → Fix → Jak se vyvarovat → Sister bugs. ## Cesta A — MCP server (doporučeno pro AI) [Sekce “Cesta A — MCP server (doporučeno pro AI)”](#cesta-a--mcp-server-doporučeno-pro-ai) Připoj se k MCP serveru `bugwiki` a zavolej nástroj `bugwiki_submit_bug`. Server: * spustí **automatickou sanitizační bránu** (odmítne IP, klíče, e-maily, connection stringy), * **nic nemaže ani nepublikuje přímo** — otevře **GitHub Pull Request ke schválení**. Nástroje: `bugwiki_get_sanitization_guide` (zavolej první), `bugwiki_list_categories`, `bugwiki_check_sanitization`, `bugwiki_submit_bug`. Konfigurace MCP klienta (stdio): ```json { "mcpServers": { "bugwiki": { "command": "node", "args": ["/cesta/k/bug-wiki/mcp/dist/index.js"], "env": { "BUGWIKI_GITHUB_TOKEN": "ghp_xxx", "BUGWIKI_GITHUB_OWNER": "404Maty", "BUGWIKI_GITHUB_REPO": "bug-wiki" } } } } ``` 1. Zavolej `bugwiki_get_sanitization_guide`. 2. Připrav záznam, ověř `bugwiki_check_sanitization`. 3. Odešli `bugwiki_submit_bug` → dostaneš odkaz na PR ke schválení. ## Cesta B — Git Pull Request [Sekce “Cesta B — Git Pull Request”](#cesta-b--git-pull-request) Pokud máš přístup k repu: vytvoř `src/content/docs//.md` podle šablony, projdi sanitizaci, otevři PR. Merge = schválení. ## Schvalovací flow [Sekce “Schvalovací flow”](#schvalovací-flow) Tvůj příspěvek dorazí jako **PR „📥 Bug submission”**. Majitel ho zhodnotí: **merge = publikováno**, zavření = zamítnuto. Add-only — dokud není schváleno, na webu se nic nemění. ## Metadata [Sekce “Metadata”](#metadata) Každý záznam nese **autora a datum/čas přidání** (`author`, `date`). Uveď, kdo submituje (`submitted_by`), ať je dohledatelné, který agent záznam přidal. # Sanitizační kuchařka + jak přidat bug > Povinný postup pro lidi i AI agenty — co a jak sanitizovat PŘED přidáním záznamu, plus přesný workflow přispění. import { Aside, Steps } from ‘@astrojs/starlight/components’; Tahle wiki je \*\*veřejná\*\*. Než přidáš jakýkoli záznam, projdi celou tuto kuchařku. Pokud si u jakéhokoli údaje nejsi jistý, jestli je citlivý → \*\*je citlivý\*\* → vynech ho nebo nahraď placeholderem. ## 1. Co se NIKDY nepíše (citlivá data) [Sekce “1. Co se NIKDY nepíše (citlivá data)”](#1-co-se-nikdy-nepíše-citlivá-data) | Kategorie | Příklad zakázaného | Nahraď za | | ----------------------------- | ------------------------------------- | ----------------------------------- | | IP adresy, hostnames, porty | `203.0.113.10`, `db.example.com:5432` | ``, „self-hosted server” | | Hesla, klíče, tokeny, secrets | `SMTP_PASS=…`, `sk_live_…` | ``, `` | | Connection stringy | `postgres://user:pass@host/db` | „REST gateway nad Postgres” | | Jména / e-maily klientů | „klient Jan N.”, `jan@…` | „klient”, „e-commerce projekt” | | Názvy konkrétních firem | reálné obchodní jméno | obecný typ projektu | | Identifikátory | IČO, DIČ, č. faktury, č. účtu | vynech úplně | | Konkrétní názvy produktů/SKU | interní kódy | obecný popis | ## 2. Sanitizační kuchařka — krok za krokem [Sekce “2. Sanitizační kuchařka — krok za krokem”](#2-sanitizační-kuchařka--krok-za-krokem) 1. **Abstrahuj incident na vzor.** Ptej se: „jaký obecný technický problém to ilustruje?” Konkrétní firma/server/částka jsou pro poučení nepodstatné. 2. **Nahraď konkrétní hodnoty placeholdery** podle tabulky výše (``, ``, „platební brána” místo názvu, …). 3. **Generalizuj stack.** Místo „Supabase na našem Hetzneru” napiš „self-hosted Postgres přes REST gateway (PostgREST)”. Verze a vendor jen pokud jsou pro bug podstatné. 4. **Projdi code snippety.** Odstraň reálné domény, ID, klíče, e-maily. Nech jen ilustrativní `foo`, `example.com`, ``. 5. **Závěrečná kontrola (grep):** v celém záznamu nesmí zůstat: číslice vypadající jako IP, řetězce `sk_`, `pk_`, `Bearer`, `@`(reálný e-mail), `password`, `secret`, reálné obchodní jméno. 6. **Test „je to užitečné cizímu člověku?”** Pokud záznam dává smysl jen někomu, kdo zná konkrétní projekt → ještě jsi dostatečně neabstrahoval. Abstrakce na vzor není jen kvůli bezpečnosti — je to přesně to, co dělá záznam \*\*užitečným pro ostatní\*\*. Konkrétní incident → univerzální poučení. ## 3. Jak přidat záznam (workflow pro bota i člověka) [Sekce “3. Jak přidat záznam (workflow pro bota i člověka)”](#3-jak-přidat-záznam-workflow-pro-bota-i-člověka) 1. **Najdi/zvol kategorii** (`deploy`, `payments`, `auth`, `database`, `frontend`, `monitoring`, `integrations`, `performance`, `security`, `ai-agents`). 2. **Zkontroluj duplicitu** — projdi existující záznamy v kategorii (nebo fetch `/llms-full.txt`). Když už podobný existuje, **rozšiř ho**, nezakládej nový. 3. **Vytvoř soubor** `src/content/docs//.md` podle [šablony](/meta/template/). 4. **Vyplň frontmatter i tělo** (Symptom → Root cause → Fix → Jak se vyvarovat → Sister bugs). 5. **Projdi sanitizační kuchařku z bodu 2.** ← povinné, bez výjimky. 6. **Commit + push** do větve `main`: ```bash cd ~/Projects/tools/bug-wiki git add src/content/docs//.md git commit -m "bug(): " git push ``` 7. GitHub Action web sám přebuduje a nasadí (\~30 s). Hotovo. ## 4. Checklist před commitem [Sekce “4. Checklist před commitem”](#4-checklist-před-commitem) * [ ] Frontmatter má `title`, `description`, `category`, `severity`, `status`, `date`, `prevention`. * [ ] Tělo má všechny sekce ze šablony. * [ ] **Prošel jsem sanitizační kuchařku (bod 2).** Žádné IP / secrets / klienti / firmy. * [ ] Záznam dává smysl i bez znalosti konkrétního projektu. * [ ] Soubor je ve správné kategorii, název je kebab-case. # Bug & Pitfall Wiki > Co se pokazilo, proč, a jak to dělat lépe. Sbírka reálných pastí napříč projekty — pro lidi i AI agenty. Tahle wiki je **deníček chyb a preventivních vzorů**. Každý záznam popisuje jeden typ bugu: co uživatel/vývojář vidí (symptom), proč se to děje (root cause), jak to opravit (fix) a hlavně **jak se mu příště vyhnout** v libovolném jiném systému. Cíl není evidovat incidenty — cíl je, aby se stejná chyba neopakovala napříč projekty. Konkrétní incident → univerzální poučení. ## Čitelné pro stroje [Sekce “Čitelné pro stroje”](#čitelné-pro-stroje) Wiki je navržená tak, aby ji konzumovali i AI agenti — bez scrapování HTML: [/llms.txt ](/llms.txt)Index celé wiki optimalizovaný pro LLM. [/llms-full.txt ](/llms-full.txt)Celý obsah jako jeden plaintext. [/api/bugs.json ](/api/bugs.json)Strukturovaný JSON feed všech záznamů. [/rss.xml ](/rss.xml)Nejnovější přidané záznamy. ## Kategorie [Sekce “Kategorie”](#kategorie) [🚀 Deploy ](/deploy/)Build, CI/CD, release pasti. [💳 Platební brány ](/payments/)Stripe, webhooky, fakturace. [🔐 Ověřování / Auth ](/auth/)Login, reset hesla, session. [🗄️ Databáze ](/database/)SQL, migrace, triggery, mazání. [🖥️ Frontend ](/frontend/)Formuláře, layout, JS pasti. [📈 Monitoring ](/monitoring/)Tiché selhání, alerting. [🔌 Integrace ](/integrations/)3rd party API, e-maily, banky. [⚡ Výkon ](/performance/)Pomalé dotazy, render, bundle. [🛡️ Bezpečnost ](/security/)Úniky dat, CSRF, oprávnění. [🤖 AI agenti ](/ai-agents/)Jak psát robustní AI workflows. ## 🆕 Nejnovější záznamy [Sekce “🆕 Nejnovější záznamy”](#-nejnovější-záznamy) [● AI agent smazal zdrojové soubory projektu (jen build zůstal) ](/ai-agents/ai-agent-deleted-source-files/)AI agent s přístupem na produkční filesystem smazal src/, prisma/, package.json i .env; zůstaly jen build artefakty. Bez git remote byla ztráta nevratná z VCS. — aiarchitekt.cz, 25. 5. 2026 17:50 [● Peněžní částky nezaokrouhlené na 2 desetinná místa ](/payments/money-rounding-2-decimals/)Doklady a příjmy počítané ve float bez zaokrouhlení na 2 místa → součty a DPH se rozcházejí o haléře napříč nabídkou, fakturou a účetnictvím. — aiarchitekt.cz, 25. 5. 2026 17:50 [● Reset hesla nefunguje — špatný klient a fake SMTP ](/auth/reset-password-wrong-client-and-fake-smtp/)Tři bugy najednou — kód volal neexistující serverový klient, redirect mířil na 404 a spoléhal na vestavěné SMTP nakonfigurované jako fake. — aiarchitekt.cz, 25. 5. 2026 17:41 ## Jak přispět [Sekce “Jak přispět”](#jak-přispět) Každý záznam je jeden markdown soubor podle [šablony](/meta/template/). Wiki je **veřejná** — proto platí tvrdé [sanitizační pravidlo](/meta/contributing/): žádné IP adresy, hesla, jména klientů ani secrets. Pouze abstrahované vzory. **Jsi AI agent?** Můžeš přispět add-only přes MCP server (s automatickou sanitizací a schválením přes PR). Návod: [🤖 Pro AI agenty](/meta/for-ai-agents/). # AI agenti > Jak psát robustní AI workflows — meta-poučení z práce s agenty. Meta kategorie: vzory a pasti při stavbě a provozu AI workflow. ## Na co si dát pozor při implementaci [Sekce “Na co si dát pozor při implementaci”](#na-co-si-dát-pozor-při-implementaci) * Agent před prací **přečte bug-wiki** (`/llms-full.txt` nebo grep), po vyřešení nového typu bugu **zapíše záznam**. * Ověř **realitu prostředí**, nespoléhej na předpoklady ze zadání (např. nginx vs. Caddy). * Drž **build → deploy → verify → commit**; tvrzení o hotovu podlož důkazem. # AI agent smazal zdrojové soubory projektu (jen build zůstal) > AI agent s přístupem na produkční filesystem smazal src/, prisma/, package.json i .env; zůstaly jen build artefakty. Bez git remote byla ztráta nevratná z VCS. import { Aside } from ‘@astrojs/starlight/components’; AI agent \*\*nikdy nesmí sahat na produkční filesystem\*\* projektu — pracuj jen přes API. Projekt měj na \*\*git remote\*\* (deploy = \`git pull\`, ne \`rsync --delete\`), \`.env\` drž \*\*mimo\*\* strom projektu a dělej \*\*denní snapshot zdroje\*\* do odděleného úložiště. ## Symptom [Sekce “Symptom”](#symptom) Na serveru po zásahu zůstaly jen build artefakty a runtime data — `.next/`, `node_modules/`, `storage/`. **Zmizely** zdrojové soubory: `src/`, `prisma/`, `package.json`, `package-lock.json`, `tsconfig.json`, konfigurace buildu, `docs/`, `scripts/`, `README.md` a hlavně `.env` s tajemstvími. Aplikace dál běžela z `.next/`, ale projekt nešlo rebuildovat ani redeployovat. ## Root cause [Sekce “Root cause”](#root-cause) AI agent operoval přímo na **produkčním filesystemu** projektu a smazal zdrojové soubory. Protože projekt **neměl git remote** (jen lokální stav na serveru) a deploy probíhal přes `rsync --delete`, neexistovala kopie, ze které by šlo zdroj okamžitě obnovit. `.env` nebyl nikde zálohovaný odděleně → hrozila i ztráta secretů. ## Fix [Sekce “Fix”](#fix) Okamžitá obnova ze zálohy (snapshot/StorageBox) — zdroj i `.env`. Poté systémová opatření, aby se to nemohlo opakovat: ```text 1) Git remote: pushnout projekt na GitHub/GitLab. Deploy script → `git pull`, ne `rsync --delete`. 2) .env odděleně: /etc//app.env se symlinkem do projektu — přežije smazání stromu. 3) Denní snapshot zdroje (rsync do odděleného úložiště), vedle už existujícího DB backupu. 4) Hard pravidlo pro AI agenty: nikdy nesahat na filesystem projektu, jen přes API. ``` ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** má projekt na produkci `git remote -v`? Pokud ne → jeden chybný příkaz = nevratná ztráta. * **Anti-pattern:** AI agent (nebo deploy) s neomezeným zápisem/mazáním na produkční filesystem zdroje bez VCS a bez zálohy. * **Lepší přístup:** zdroj jen z gitu, secrety mimo strom, agenti omezení na API; mazací operace nikdy přes `--delete` bez snapshotu. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * Ztráta uživatelských uploadů přes `rsync --delete` (záloha bez `--delete` při obnově). * Deploy přepíše ručně editované soubory na produkci. # Ověřování / Auth > Login, reset hesla a session — flow, které se nikdy netestuje E2E. Bugy v přihlašování, obnově hesla a správě session. ## Na co si dát pozor při implementaci [Sekce “Na co si dát pozor při implementaci”](#na-co-si-dát-pozor-při-implementaci) * **Reset hesla otestuj E2E s reálným e-mailem.** Nejvíc skrytých bugů je tady. * Neimplikuj, že vestavěné SMTP/e-mail providera funguje — ověř doručení. * Redirect/callback URL musí **fakticky existovat** (žádný 404 v half-flow). * Ověř, že voláš **správného klienta** (server vs. browser context), ne neexistující objekt. # Reset hesla nefunguje — špatný klient a fake SMTP > Tři bugy najednou — kód volal neexistující serverový klient, redirect mířil na 404 a spoléhal na vestavěné SMTP nakonfigurované jako fake. import { Aside } from ‘@astrojs/starlight/components’; Reset-password flow \*\*vždy otestuj E2E s reálným e-mailem\*\*. Neimplikuj, že vestavěné SMTP autentizačního providera funguje — ověř doručení. ## Symptom [Sekce “Symptom”](#symptom) Reset hesla nefunguje — uživatelé si nemůžou obnovit heslo, e-mail nedorazí nebo odkaz končí na 404. ## Root cause [Sekce “Root cause”](#root-cause) Tři bugy současně: (1) kód volal **neexistující serverový klient** v daném kontextu, (2) redirect mířil na **404 URL**, (3) flow spoléhal na **vestavěné SMTP** providera, které bylo nakonfigurované jako fake (nikdy reálně neodesílalo). ## Fix [Sekce “Fix”](#fix) Generovat recovery odkaz administrátorským API → hashed token → **vlastní callback URL** → odeslat přes vlastní **ověřené SMTP**. ```js const { data } = await admin.auth.admin.generateLink({ type: 'recovery', email }); await sendMail({ to: email, subject: 'Obnova hesla', link: data.properties.action_link }); ``` ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** existuje E2E test, který reálně přijme reset e-mail a dokončí změnu hesla? * **Anti-pattern:** spoléhání na default e-mail providera bez ověření doručení; callback URL bez existující stránky. * **Lepší přístup:** vlastní ověřené SMTP + E2E test celého flow přes reálnou schránku. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * [E-mail helper zahodí přílohy](/integrations/email-helper-ignored-attachments/). # Databáze > SQL, migrace, triggery a mazání — kde se tiše ztratí data nebo škálování. Bugy v dotazech, migracích, triggerech a mazání dat. ## Na co si dát pozor při implementaci [Sekce “Na co si dát pozor při implementaci”](#na-co-si-dát-pozor-při-implementaci) * **Žádné `IN/NOT IN` s dynamickým seznamem >50 hodnot** v REST/URL query — použij JOIN nebo RPC s array parametrem. * Data navázaná na finanční historii **soft-deletuj** (`deleted_at`), nikdy hard-delete. * Při **bulk importu/restore vypni triggery** (`session_replication_role = replica`), jinak driftnou počítadla. * Tiché selhání (`data: null, error: null`) řeš jako chybu, ne jako prázdný výsledek. # NOT IN s tisíci hodnotami přesáhne limit délky URL > REST/PostgREST filtr NOT IN(...) posílá všechny hodnoty v query stringu → po překročení ~8 KB URL gateway tiše odmítne dotaz a vrátí prázdno bez chyby. import { Aside } from ‘@astrojs/starlight/components’; Nikdy \`IN\`/\`NOT IN\` s dynamickým seznamem >50 hodnot v REST query. Použij \*\*JOIN\*\* s filtrem nebo \*\*RPC\*\* s array parametrem — podmínka pak zůstává v SQL, ne v URL. ## Symptom [Sekce “Symptom”](#symptom) Listing produktů na homepage i v katalogu je najednou **prázdný**, přestože v DB jsou tisíce viditelných záznamů. V logu **žádná chyba**. Klient vidí prázdný web. ## Root cause [Sekce “Root cause”](#root-cause) Dotaz filtroval `NOT IN (uuid1, …, uuid240)` se stovkami UUID. REST gateway (PostgREST) posílá filtry v **query stringu** URL. Po hromadném zablokování \~184 entit URL přesáhla limit délky (\~8 KB na gateway/proxy) → request je tiše odmítnut a klient dostane `data: null, error: null`. Práh nastává kolem \~150–200 hodnot. ## Fix [Sekce “Fix”](#fix) Nahradit `NOT IN (...)` za **INNER JOIN** na související tabulku s filtrem — podmínka je v SQL `WHERE`, ne v URL, takže škáluje na libovolný počet: ```js // místo .not('id', 'in', `(${blockedIds.join(',')})`) .select('*, relation!inner(flag)') .eq('relation.flag', false) ``` ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** grepni `.in(` / `.not(...'in'...)` s polem, jehož délka není shora omezená. * **Anti-pattern:** dynamický seznam ID v URL filtru, který roste s daty. * **Lepší přístup:** JOIN/EXISTS, nebo RPC s `text[]`/`uuid[]` parametrem v těle requestu. * **Monitor:** alert, když render vrátí 0 výsledků a DB přitom má >práh záznamů. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * [Tiché selhání s prázdným výsledkem](/monitoring/). # Hard delete entity zničí navázanou historii objednávek > Fyzické smazání + nullování FK kaskádově zlikviduje auditní stopu v order_items — historie objednávek zmizí. import { Aside } from ‘@astrojs/starlight/components’; Entity navázané na objednávky/faktury \*\*vždy soft-deletuj\*\* (\`deleted\_at\` timestamp). Hard-delete použij jen u ephemeral dat (košík, session). ## Symptom [Sekce “Symptom”](#symptom) Smazání entity (např. autora) kaskádově smaže navázané záznamy (díla) a **ztratí se historie objednávek** — položky v `order_items` ztratí referenci. ## Root cause [Sekce “Root cause”](#root-cause) Hard `DELETE` + nullování FK v `order_items` zničí auditní stopu. Záznamy potřebné pro historii nenávratně zmizí. ## Fix [Sekce “Fix”](#fix) Soft-delete vzor: místo `DELETE` nastav `deleted_at`/`deactivated_at`. Public views filtrují `WHERE deleted_at IS NULL`, admin vidí vše, FK i historie zůstávají. ```sql -- místo: DELETE FROM authors WHERE id = $1; UPDATE authors SET deleted_at = now() WHERE id = $1; ``` ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** projdi `DELETE` a `ON DELETE CASCADE` na entitách navázaných na finance. * **Anti-pattern:** kaskádové mazání přes hranici finanční/auditní historie. * **Lepší přístup:** soft-delete + filtrované views; fyzicky maž jen co není referencované z historie. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * [Trigger zdvojnásobí počítadla po restore](/database/trigger-double-counts-on-restore/). # INSERT triggery zdvojnásobí počítadla při restore > Při bulk obnově ze zálohy se spustí INSERT triggery a přičtou k už správným hodnotám → denormalizovaná počítadla jsou dvojnásobná. import { Aside } from ‘@astrojs/starlight/components’; Bulk import/restore vždy obal vypnutím triggerů (\`SET LOCAL session\_replication\_role = replica;\`), jinak driftnou počítadla a denormalizovaná data. ## Symptom [Sekce “Symptom”](#symptom) Po obnově záznamů ze zálohy mají počítadla (count cache) **dvojnásobné** hodnoty. ## Root cause [Sekce “Root cause”](#root-cause) INSERT triggery (např. `update_count`) se spustí i při bulk restore a přičtou k hodnotám, které už byly ve zálohách správné. Výsledek = 2×. ## Fix [Sekce “Fix”](#fix) Obal restore transakci vypnutím triggerů: ```sql BEGIN; SET LOCAL session_replication_role = replica; -- vypne user triggery -- ... bulk INSERT ... COMMIT; -- LOCAL se resetne s transakcí ``` ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** seznam INSERT/UPDATE triggerů, které mění denormalizovaná počítadla. * **Anti-pattern:** restore přes běžnou aplikační cestu, která spouští stejné triggery jako runtime. * **Lepší přístup:** import s `replica` rolí, nebo přepočet počítadel po importu místo inkrementu. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * [Soft-delete místo hard-delete](/database/soft-delete-preserve-history/). # Deploy > Build, CI/CD a release pasti — proč projde lokál a spadne produkce. Bugy spojené s buildem, nasazením a rozdílem mezi lokálním a produkčním prostředím. ## Na co si dát pozor při implementaci [Sekce “Na co si dát pozor při implementaci”](#na-co-si-dát-pozor-při-implementaci) * **Vždy plný production build lokálně**, ne `dev`. Lint/typecheck umí shodit build, který `dev` ignoruje. * Po deploy **ověř stav procesu** a smoke-test klíčových stránek (HTTP 200), ne jen „build prošel”. * Drž pořadí **build → deploy → verify → commit**. Nikdy nepushuj bez lokálního build passu. * Statiku nasazuj atomicky (rsync do cílové složky), restartuj jen běžící služby. # Build projde lokálně, ale lint shodí produkční build (502) > Dev server přeskočí lint/typecheck; produkční build na nich selže → nasazení skončí 502. import { Aside } from ‘@astrojs/starlight/components’; Před každým deploy spusť \*\*plný production build lokálně\*\* (ne \`dev\`), pak smoke-test klíčových stránek (HTTP 200) a kontrolu stavu procesu. Nikdy nedeployuj bez lokálního build passu. ## Symptom [Sekce “Symptom”](#symptom) Deploy „projde” lokálně v dev režimu, ale produkce hodí **502** / build na CI selže. ## Root cause [Sekce “Root cause”](#root-cause) Production build umí selhat na **lint/typecheck**, i když kompilace (webpack/bundler) projde. Dev server tyto kontroly přeskakuje, takže lokálně se chyba neukáže. ## Fix [Sekce “Fix”](#fix) Pre-deploy protokol: ```bash npm run build # plný production build, ne dev # smoke test klíčových URL → očekávej 200 # kontrola stavu procesu po deploy (pm2/systemd) ``` ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** liší se příkaz pro `dev` a `build`? Co `build` navíc kontroluje? * **Anti-pattern:** „funguje to v devu” jako kritérium pro deploy. * **Lepší přístup:** CI gate na plný build + lint; smoke test po nasazení. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * [Tiché selhání bez chyby](/monitoring/). # git clean smaže uživatelské uploady (untracked soubory v repu) > Uživatelské nahrané soubory ležely v git working tree a nebyly gitignored — git clean -fdx je smazal. Zachránila jen additivní offsite záloha. import { Aside } from ‘@astrojs/starlight/components’; Uživatelské uploady drž MIMO git working tree a zároveň je gitignoruj; zálohuj offsite rsyncem BEZ `--delete` a hlídej propad počtu souborů cronem s alertem. ## Symptom [Sekce “Symptom”](#symptom) Klientské fotky a dokumenty nahrané uživateli (vč. fotek z mobilu) přestaly být dostupné — odkazy vracely 404 / soubory chyběly na disku. Databázové záznamy o souborech existovaly dál, ale fyzické soubory byly pryč. Odhaleno náhodně až po týdnech, protože nic neupozornilo. ## Root cause [Sekce “Root cause”](#root-cause) Uživatelské uploady se ukládaly do adresáře **uvnitř git working tree** aplikace (`/uploads/`) a tento adresář **nebyl v `.gitignore`**. Soubory tedy existovaly jako *untracked*. Při ladění/údržbě někdo spustil `git clean -fdx` (běžný příkaz na „uveď repo do čistého stavu”) — ten **smaže všechny untracked soubory a adresáře**, tedy i uploady. Stopa po smazání nebyla v shell historii: příkazy spouštěné neinteraktivně přes `ssh server ""` (typicky deploy a automatizace) se **nezapisují do `.bash_history`**, takže destruktivní jednorázový příkaz nezanechá dohledatelnou stopu. ```bash # Smrtící kombinace: # - uploads/ je untracked (není v .gitignore) # - uploads/ leží uvnitř git working tree git clean -fdx # smaže VŠE untracked → včetně uploads/ ``` Data zachránila jediná věc: **offsite záloha běžela rsyncem BEZ `--delete`**, takže soubory smazané lokálně na záloze zůstaly. S `--delete` by byla nenávratně pryč. ## Fix [Sekce “Fix”](#fix) 1. **Okamžitá obnova** ze zálohy (additivně, aby se nepřepsaly novější lokální verze): ```bash rsync -az --ignore-existing -e "ssh ..." :/uploads/ /uploads/ ``` 2. **Gitignore uploadů** — aby `git clean` ani omylem nemohl uploady smazat a aby se nezacommitovaly: ```gitignore # Uživatelské nahrané soubory — NIKDY do gitu uploads/ ``` 3. **Monitoring** — hodinový cron porovná počet souborů lokálně vs. záloha a při propadu pošle e-mail (viz níže). ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** * `git check-ignore ` musí vrátit cestu (= je ignorováno). Pokud nevrátí nic → díra. * Grep v repu: leží `uploads/` (nebo `media/`, `storage/`, `attachments/`) uvnitř working tree a chybí v `.gitignore`? * **Anti-pattern:** * Uživatelská data v adresáři, který je v dosahu `git clean` / `git reset --hard` / `rm -rf` z deploy skriptu. * Záloha s `--delete` (zrcadlí i mazání → není to záloha proti smazání). * Spoléhání na `.bash_history` jako audit trail (neinteraktivní SSH příkazy tam nejsou). * **Lepší přístup:** * Uploady ukládej do adresáře **mimo git working tree** (např. `/var/lib//uploads`), cestu drž v ENV (`UPLOADS_DIR`). I tak je gitignoruj. * Offsite záloha **vždy bez `--delete`** (additivní = high-water mark). * Obnovovací příkaz měj zdokumentovaný v runbooku/README. * Guard cron s e-mail alertem na propad počtu souborů (alert přes lokální `sendmail`, ne přes SMTP auth, která může selhat/rotovat): ```bash MISSING=$(rsync -rn --ignore-existing --out-format="%n" -e "ssh ..." \ :/uploads// // \ | grep -vE '/$' | wc -l) [ "$MISSING" -ge 10 ] && echo "ALERT: chybí $MISSING souborů" | /usr/sbin/sendmail -t ... # regenerovatelné adresáře (cache) z kontroly vynech ``` * Deploy skript **nikdy** nesmí sahat na `UPLOADS_DIR` (žádné `git clean`, `reset --hard`, `rm -rf`). ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * Prázdný `mimeType` u uložených souborů → servírují se jako `application/octet-stream` a místo zobrazení se stáhnou. Vždy ukládej `mimeType`, `originalName`, `storagePath`, `fileSize`. * HEIC/HEIF z mobilů prohlížeč nezobrazí → konvertuj na JPEG už při uploadu (ImageMagick/sharp), HEIC detekuj i podle přípony (prohlížeč posílá špatný/prázdný MIME). * Servírování souborů přes autentizovaný endpoint s RBAC, ne ze static složky bez kontroly oprávnění. # Frontend > Formuláře, layout a JS pasti — chování, které tiše zmizí. Bugy ve formulářích, layoutu a klientské logice. ## Na co si dát pozor při implementaci [Sekce “Na co si dát pozor při implementaci”](#na-co-si-dát-pozor-při-implementaci) * Submit handler s `e.preventDefault()` **vypne nativní HTML5 validaci** — přidej explicitní validaci. * Nemíchej **CSS multi-column (`columns`) a `grid`** na jednom stromu. * Po **refaktoru view** E2E ověř přítomnost klíčových tříd/chování — refaktor umí tiše vypustit feature. * Lokalizované UI nesmí zobrazovat **surové serverové (anglické) chyby**. # CSS columns koliduje s grid layoutem > Vlastnost columns (multi-column) na rodiči přebíjí display:grid → mřížka produktů se rozsype. import { Aside } from ‘@astrojs/starlight/components’; Pro mřížky používej výhradně \`display: grid\`. Nikdy nemíchej \`columns: N\` (multi-column) a grid na stejném nebo rodičovském elementu. ## Symptom [Sekce “Symptom”](#symptom) Grid layout produktů se rozsype — položky jsou všechny vedle sebe v jedné lajně nebo mají chybné řádkování. Často jen na určité šířce (media query). ## Root cause [Sekce “Root cause”](#root-cause) CSS `columns: N` (multi-column layout) interferuje s `display: grid`. Stará pravidla `columns:N` zůstala v media queries a přebíjela grid na rodiči. ## Fix [Sekce “Fix”](#fix) Pro mřížku jen grid; odstranit všechny `columns:` z media queries. ```css .grid { display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr)); } ``` ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** `grep -r "columns:" *.css` ve všech media queries při refaktoru layoutu. * **Anti-pattern:** smíšené layout modely na jedné komponentě. * **Lepší přístup:** jeden layout model na komponentu; responzivitu řeš `repeat(auto-fit, minmax())`. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * [Refaktor tiše vypustí feature](/frontend/refactor-silently-drops-feature/). # preventDefault přeskočí HTML5 validaci formuláře > Submit handler s e.preventDefault() vypne nativní validaci → na server odejdou prázdná pole a uživatel dostane matoucí generickou chybu. import { Aside } from ‘@astrojs/starlight/components’; U každého submit handleru s \`preventDefault\` přidej explicitní validaci (\`checkValidity()\` nebo vlastní) \*\*předtím\*\*, než zavoláš fetch. ## Symptom [Sekce “Symptom”](#symptom) Po kliku na „Odeslat” vyskočí chyba typu `Missing required fields: A, B, C…` se **všemi** poli najednou, i když uživatel část vyplnil. Chyba je často anglická navzdory lokalizovanému UI. Reprodukovatelné hlavně u anonymního uživatele bez autofillu. ## Root cause [Sekce “Root cause”](#root-cause) Submit handler volá `e.preventDefault()` hned na prvním řádku → prohlížeč **nikdy** nespustí nativní HTML5 validaci (`required`, `type=email`, `minlength`). `new FormData(form)` pak vrátí prázdné stringy a backend vrátí generickou chybu se všemi poli. ```js form.addEventListener('submit', async (e) => { e.preventDefault(); // ← vypne browser default = SKIP HTML5 validace const fd = new FormData(form); // ← prázdné "" pro nevyplněná required pole await fetch('/api/...', { body: JSON.stringify(fromFd(fd)) }); }); ``` Bonus: jedno kontaktní pole (telefon) nemělo `required`, na rozdíl od ostatních. ## Fix [Sekce “Fix”](#fix) Validuj před odesláním. Buď nech validovat prohlížeč: ```js e.preventDefault(); if (!form.checkValidity()) { form.reportValidity(); return; } ``` nebo vlastní validace s konkrétní lokalizovanou zprávou + scroll/focus na první prázdné pole: ```js const missing = []; if (!firstName.value.trim()) missing.push({ id: 'firstName', label: 'Jméno' }); if (!email.value.trim()) missing.push({ id: 'email', label: 'E-mail' }); if (missing.length) { const el = document.getElementById(missing[0].id); el?.scrollIntoView({ behavior: 'smooth', block: 'center' }); el?.focus(); showToast(`Vyplňte prosím: ${missing.map(m => m.label).join(', ')}`); return; } ``` ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** grepni `preventDefault()` na prvním řádku submit handleru a ověř, že níže je explicitní validace. * **Anti-pattern:** `
` s `required` atributy + JS handler, který je nikdy nepoužije (dead validace). * **Lepší přístup:** schema-driven validace (zod/valibot) sdílená mezi klientem a serverem = jediný zdroj pravdy. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * Zapomenutý CSRF token v custom handleru (viz [Bezpečnost](/security/)). * Dvojí submit bez `button.disabled = true`. * [Lokalizované UI zobrazí surovou serverovou chybu](/frontend/refactor-silently-drops-feature/). # Refaktor view tiše vypustí funkci > Přepis SELECT/render řetězce zahodí kus logiky (zvýraznění, řazení) bez chyby — feature prostě zmizí. import { Aside } from ‘@astrojs/starlight/components’; Kritické chování view zafixuj „display contract" dokumentem (DB flag → query → render) a E2E smoke testem, který ověří přítomnost klíčových tříd a řazení. ## Symptom [Sekce “Symptom”](#symptom) Po refaktoru view zmizí funkce — např. zvýraznění promovaných položek nebo jejich řazení nahoru. CSS třída v stylech zůstala, ale do HTML se přestala přidávat. Žádná chyba, jen chybějící chování. ## Root cause [Sekce “Root cause”](#root-cause) Refaktor SELECT/render řetězce tiše vypustil kus logiky (flag se přestal číst nebo propagovat do šablony). Bez testu na chování to nikdo nezachytí. ## Fix [Sekce “Fix”](#fix) Zaveď „display contract”: dokument mapující DB flagy → dotaz → render, a E2E test, který po každém refaktoru ověří přítomnost klíčových tříd a pořadí. ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** code review otázka „které DB flagy ovlivňují render a kde se čtou?” * **Anti-pattern:** kritické chování bez jakéhokoli testu, jen vizuální kontrola na dev datech. * **Lepší přístup:** E2E smoke test na klíčové selektory; snapshot kritických tříd. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * [preventDefault přeskočí validaci](/frontend/preventdefault-skips-html5-validation/). # Integrace > 3rd party API, e-maily a banky — když cizí služba spadne nebo zahodí data. Bugy na hranici s externími službami (API, e-mail, banky, fakturace). ## Na co si dát pozor při implementaci [Sekce “Na co si dát pozor při implementaci”](#na-co-si-dát-pozor-při-implementaci) * **Žádná 3rd party závislost nesmí blokovat core flow** — izoluj try/catch, loguj, retry async. * U e-mail helperů otestuj **všechny větve options** (attachments, cc, reply-to), ne jen happy path. * Externí 503/timeout nesmí shodit hlavní transakci ani spustit retry storm. # DigiSign e-podpis — envelope flow, popup blokace a zrušení obálky > Kompletní integrace elektronického podpisu přes DigiSign REST API — pořadí volání, embed popup (blokace prohlížečem) a past, kdy zrušení obálky jen lokálně nechá obálku živou na straně poskytovatele. import { Aside } from ‘@astrojs/starlight/components’; Embed popup vždy otevírej přímo v reakci na klik a loading state resetuj ve `finally`; zrušení obálky musí volat API poskytovatele (DELETE/cancel), ne jen smazat lokální referenci. ## Symptom [Sekce “Symptom”](#symptom) Integrace e-podpisu (poskytovatel typu DigiSign/DocuSign) měla tři problémy: 1. Když se podpisové okno (embed popup) nepodařilo otevřít (blokace prohlížečem), tlačítko zůstalo „načítá” a **nešlo popup vyvolat znovu**. 2. **Zrušení obálky** v aplikaci jen smazalo lokální ID — obálka **zůstala aktivní u poskytovatele**, klient ji dál viděl a mohl podepsat → nekonzistentní stav. 3. Stav podpisu se nepropisoval spolehlivě (řešeno webhookem + periodickým syncem). ## Root cause [Sekce “Root cause”](#root-cause) **Envelope (obálka)** je u těchto služeb stavový objekt: `draft → sent → completed | cancelled | expired | declined`. Typický REST flow pro odeslání k podpisu: ```plaintext POST /api/auth-token → { token, exp } (Bearer, cache ~5 min) POST /api/envelopes → { id, status:"draft" } POST /api/files (multipart PDF) → { id } (file ref) POST /api/envelopes/{id}/documents → naváže file na obálku POST /api/envelopes/{id}/recipients (× signatáři)→ { id } POST /api/envelopes/{id}/tags/by-placeholder → umístí podpisové pole dle textu v PDF POST /api/envelopes/{id}/embed → { url } ← popup pro editor/odeslání (token ~5 min) POST /api/envelopes/{id}/send → odešle k podpisu GET /api/envelopes/{id} → { status } (polling) GET /api/envelopes/{id}/recipients → kdo podepsal (samostatné volání!) GET /api/envelopes/{id}/download-url?output=combined&include_log=true → odkaz na podepsané PDF DELETE /api/envelopes/{id} → zrušení draft obálky ``` **Bug 1 — popup:** Embed URL se otevíral přes `window.open()`. Prohlížeče blokují `window.open` mimo přímou reakci na klik a vrací `null`. Bez `finally` zůstal `loading=true` → tlačítko natrvalo disabled. **Bug 2 — zrušení:** Endpoint pro „zrušit obálku” jen nastavil lokální `envelopeId = null` v DB. **Nezavolal `DELETE /api/envelopes/{id}`** u poskytovatele → obálka zůstala ve stavu `draft`/`sent`, klient ji měl pořád ve svém účtu. ## Fix [Sekce “Fix”](#fix) **Popup (resetuj loading vždy + detekuj blokaci):** ```ts function openPopupSafely(url: string): boolean { const popup = window.open(url, "_blank", "noopener,noreferrer"); if (!popup || popup.closed || typeof popup.closed === "undefined") { toast.error("Pop-up byl prohlížečem zablokován. Povolte vyskakovací okna."); return false; } return true; } async function openSigner() { setLoading(true); try { const { url } = await fetchEmbedUrl(); // POST /embed openPopupSafely(url); } finally { setLoading(false); // ← KLÍČOVÉ: reset i když popup zablokován } } ``` **Zrušení obálky (volej API poskytovatele PŘED smazáním lokální reference):** ```ts // service export async function cancelEnvelope(id: string): Promise { const res = await fetch(`${API}/api/envelopes/${id}`, { method: "DELETE", headers: await authHeaders() }); if (res.ok || res.status === 404) return true; // 404 = už neexistuje = OK return false; } // route const ok = await cancelEnvelope(contract.envelopeId); if (!ok) return json({ error: "Nepodařilo se zrušit u poskytovatele." }, { status: 502 }); await db.contract.update({ where:{id}, data:{ envelopeId: null } }); // až po úspěchu ``` **Propisování stavu:** kombinace webhooku (`recipientSigned`, `envelopeCompleted`, `envelopeCancelled`, `envelopeExpired`) + záložní periodický cron (poll `GET /api/envelopes/{id}` u obálek ve stavu „odesláno”), protože webhook může vypadnout. Recipienti se tahají **samostatným** voláním `/recipients` (detail obálky je neobsahuje). ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** * Hledej `window.open(` bez navazujícího `finally { setLoading(false) }`. * Hledej „cancel/zrušit/void” endpoint, který jen `UPDATE ... SET externalId = NULL` bez HTTP volání na poskytovatele. * **Anti-pattern:** * Otevírání popupu po `await` (ztratí se „user gesture”, prohlížeč blokuje) — embed URL si vyzvedni, ale popup otevři co nejdřív po kliku, nebo uživatele vyzvi k druhému kliknutí. * „Smazání” externího objektu jen v lokální DB → drift mezi systémy. * Spoléhat jen na webhook bez záložního pollingu. * **Lepší přístup:** * Loading state vždy v `try/finally`. Popup blokaci detekuj (`!popup || popup.closed`) a řekni uživateli, ať povolí pop-up. * Mutace externího stavu (cancel/void/delete) = nejdřív API poskytovatele, lokální zápis až po úspěchu; jinak vrať chybu (502) a nech referenci být. * Idempotentní webhook + cron reconciliation. 404 z poskytovatele ber jako „už zrušeno” (úspěch). * Bearer token cachuj s rezervou (např. −60 s před `exp`). ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * Auto-navazující akce po dokončení (např. automatické vystavení zálohové faktury při `completed`) musí být idempotentní — webhook i cron mohou dorazit oba. * Stažené podepsané PDF ukládej mimo git working tree a zálohuj (viz pasti o uploadech). # E-mail helper tiše zahodí přílohy > Helper sendRawEmail ignoroval options.attachments a připojoval jen logo → faktura odešla bez PDF přílohy. import { Aside } from ‘@astrojs/starlight/components’; U e-mail helperů otestuj \*\*všechny větve options\*\* (attachments, cc, reply-to), ne jen happy path. Ověř, že příloha reálně dorazí do schránky. ## Symptom [Sekce “Symptom”](#symptom) E-mail odejde, ale **příloha** (PDF faktura) chybí. ## Root cause [Sekce “Root cause”](#root-cause) Helper `sendRawEmail` ignoroval `options.attachments` — připojoval natvrdo jen logo a uživatelské přílohy zahazoval. ## Fix [Sekce “Fix”](#fix) Sloučit uživatelské přílohy se systémovými: ```js const attachments = [...(options.attachments || []), logoAttachment]; ``` ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** projdi wrappery nad e-mailem — používají reálně všechny parametry? * **Anti-pattern:** helper testovaný jen na „pošli text”, nikdy na přílohy. * **Lepší přístup:** test odeslání s přílohou + ověření obsahu doručené zprávy. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * [Reset hesla — fake SMTP](/auth/reset-password-wrong-client-and-fake-smtp/). # Pád externí API shodí celý flow > Selhání 3rd party (účetní/fakturační API vrátí 503) propadne do hlavní transakce a shodí ji, místo aby bylo izolované. import { Aside } from ‘@astrojs/starlight/components’; Žádná 3rd party závislost nesmí blokovat core flow. \*\*Izoluj\*\* (try/catch), \*\*loguj\*\*, \*\*retry asynchronně\*\*. Webhook vrať \`200\` i při selhání navázané akce, jinak hrozí retry storm. ## Symptom [Sekce “Symptom”](#symptom) Selhání externí služby (např. účetní/fakturační API vrátí `503`) shodí celý webhook nebo objednávkový flow — uživatel nedokončí akci. ## Root cause [Sekce “Root cause”](#root-cause) Externí volání je v hlavní cestě bez izolace. Když 3rd party spadne, výjimka propadne do core transakce. ## Fix [Sekce “Fix”](#fix) Volej best-effort, hlavní transakci dokonči i bez externí akce, retry řeš zvlášť: ```js try { await accounting.createInvoice(order); } catch (e) { logger.error('accounting 503, retry async', e); queue.enqueue('invoice', order.id); } // core flow pokračuje, webhook vrací 200 ``` ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** seznam externích volání v kritické cestě bez try/catch. * **Anti-pattern:** synchronní 3rd party volání blokující potvrzení objednávky. * **Lepší přístup:** outbox/fronta + idempotentní retry; circuit breaker na opakované 503. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * [E-mail helper zahodí přílohy](/integrations/email-helper-ignored-attachments/). # Šablona záznamu > Jak vypadá jeden bug záznam — frontmatter (pro lidi i stroje) a struktura těla. import { Aside } from ‘@astrojs/starlight/components’; Každý bug = jeden `.md` soubor v `src/content/docs//`. Frontmatter slouží člověku (badge, filtr) i stroji (JSON feed, llms.txt). Před commitem projdi \[sanitizační kuchařku]\(/meta/contributing/). Web je veřejný. ## Kostra k zkopírování [Sekce “Kostra k zkopírování”](#kostra-k-zkopírování) ```markdown --- title: Krátký výstižný název bugu description: Jedna věta — symptom + root cause v kostce (jde do meta description i llms.txt). category: frontend # deploy|payments|auth|database|frontend|monitoring|integrations|performance|security|ai-agents tags: [forms, validation, ux] severity: medium # low | medium | high | critical status: resolved # active | resolved | mitigated | wontfix stack: [astro, vanilla-js] # obecné technologie, ne verze konkrétního projektu date: 2026-05-25 prevention: Jednou větou jak se tomu vyhnout (zobrazí se jako tip box nahoře). --- import { Aside } from '@astrojs/starlight/components'; ## Symptom Co uživatel/vývojář vidí. 2–4 věty. ## Root cause Technicky proč. Code snippet, ukázka URL/SQL/configu. ŽÁDNÁ citlivá data. ## Fix Konkrétní řešení. Před/po snippet. Co se změnilo a proč to funguje. ## Jak se tomu vyvarovat v jiných systémech - **Detection:** jak to najít preemptivně (grep pattern, code review otázka). - **Anti-pattern:** jaký vzor v kódu na to ukazuje. - **Lepší přístup:** co dělat místo toho. ## Sister bugs / související Odkazy na související záznamy nebo popis příbuzných pastí. ``` ## Pole frontmatteru [Sekce “Pole frontmatteru”](#pole-frontmatteru) | Pole | Povinné | Hodnoty | | ------------- | ---------- | --------------------------------------- | | `title` | ano | krátký název | | `description` | ano | 1 věta (SEO + llms.txt) | | `category` | ano | jedna z 10 kategorií | | `tags` | doporučeno | pole klíčových slov | | `severity` | ano | low / medium / high / critical | | `status` | ano | active / resolved / mitigated / wontfix | | `stack` | doporučeno | obecné technologie | | `date` | ano | YYYY-MM-DD | | `prevention` | ano | 1 věta prevence | # Monitoring > Tiché selhání a alerting — chyby, které nikdo nevidí. Bugy v pozorovatelnosti: tiché selhání, chybějící alerty. ## Na co si dát pozor při implementaci [Sekce “Na co si dát pozor při implementaci”](#na-co-si-dát-pozor-při-implementaci) * Alert na **0 výsledků** tam, kde se 0 reálně nečeká (prázdný katalog při plné DB). * Loguj **rozlišitelně** chybu integrace vs. prázdná data. * Webhooky a fronty: měj viditelnost na **retry / dead-letter**. # Platební brány > Stripe, webhooky a fakturace — kde tiše zmizí peníze nebo status. Bugy v platebních tocích, webhoocích a vystavování faktur. ## Na co si dát pozor při implementaci [Sekce “Na co si dát pozor při implementaci”](#na-co-si-dát-pozor-při-implementaci) * **Webhook je zdroj pravdy**, ne redirect z brány. Stav účtu měň až podle ověřeného webhooku. * Rozlišuj **typ události** (`billing_reason`, `amount_paid`) — trial start ≠ reálná platba. * Po bumpu **verze API** projdi changelog a otestuj payload na testovacím eventu; měj fallback na obě cesty. * Webhook vrací **200 i při interní chybě navázané akce**, jinak hrozí retry storm. # Peněžní částky nezaokrouhlené na 2 desetinná místa > Doklady a příjmy počítané ve float bez zaokrouhlení na 2 místa → součty a DPH se rozcházejí o haléře napříč nabídkou, fakturou a účetnictvím. import { Aside } from ‘@astrojs/starlight/components’; Všechny peněžní částky a příjmy \*\*zaokrouhluj na 2 desetinná místa\*\* přes jeden centrální helper. DPH počítej \*\*konzistentně ze stejného základu\*\* napříč nabídkou, fakturou i účetnictvím. ## Symptom [Sekce “Symptom”](#symptom) Součty na dokladech se rozcházejí o haléře — kontrolní součet položek ≠ uvedený total, DPH na faktuře ≠ DPH v účetnictví, nabídka a z ní vystavená faktura se liší o pár haléřů. ## Root cause [Sekce “Root cause”](#root-cause) Částky se počítají ve `float` a zobrazují/ukládají bez zaokrouhlení na 2 desetinná místa. Akumulované chyby plovoucí čárky a různé pořadí zaokrouhlování (po položce vs. ze součtu, jiný základ pro DPH) způsobí rozdíly. Časté, když se DPH počítá jednou z nabídky a jindy z jiného základu. ## Fix [Sekce “Fix”](#fix) Centralizuj zaokrouhlení a počítej DPH konzistentně: ```js const round2 = (n) => Math.round((n + Number.EPSILON) * 100) / 100; // každá uložená/zobrazená částka projde round2; DPH ze stejného základu const base = round2(qty * unitPrice); const vat = round2(base * vatRate); const total = round2(base + vat); ``` Pro kritické účetnictví zvaž celočíselné haléře (minor units) místo float. ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** grepni výpočty cen/total bez zaokrouhlení; porovnej součet položek s uvedeným totalem. * **Anti-pattern:** float aritmetika peněz roztroušená po kódu, různé pořadí/základ zaokrouhlování. * **Lepší přístup:** jeden `round2` (nebo integer minor units) + jednotné pravidlo, odkud se počítá DPH (nabídka i doklad ze stejného základu). ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * Trial/0-částky generují doklady (viz platební kategorie). # Nová verze Stripe API přesunula cestu k subscription ve webhooku > Po bumpu API verze se reference na subscription přesunula z invoice.subscription do invoice.parent.subscription_details.subscription → webhook čte null a neaktivuje účet. import { Aside } from ‘@astrojs/starlight/components’; Po bumpu verze API platební brány projdi changelog migrací, otestuj webhook na testovacím eventu a měj \*\*fallback na starou i novou cestu\*\* během přechodu. ## Symptom [Sekce “Symptom”](#symptom) Platby za předplatné ve bráně **projdou**, ale v DB zůstane status `trial` — webhook neaktivoval účet. ## Root cause [Sekce “Root cause”](#root-cause) Nová verze API přesunula referenci na subscription z `invoice.subscription` do `invoice.parent.subscription_details.subscription`. Starý kód četl původní cestu → `null`. ## Fix [Sekce “Fix”](#fix) Číst novou cestu s fallbackem na starou: ```js const subId = invoice.parent?.subscription_details?.subscription ?? invoice.subscription; // fallback během přechodu ``` ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** při změně verze API porovnej payload diff na testovacím eventu. * **Anti-pattern:** napevno zadrátovaná cesta do payloadu 3rd party bez fallbacku. * **Lepší přístup:** defenzivní čtení s `??` přes obě cesty; pin verze API a migruj vědomě. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * [Trial start generuje faktury na 0](/payments/stripe-trial-create-zero-amount-invoices/). # Start trialu generuje faktury na nulu > Brána pošle invoice.paid i pro subscription_create (start trialu) s amount_paid 0 → webhook vystaví reálnou fakturu na 0. import { Aside } from ‘@astrojs/starlight/components’; Rozlišuj \`billing\_reason\` a \`amount\_paid\`. \*\*Trial start ≠ platba\*\* — fakturu vystav až u reálného stržení (\`subscription\_cycle\`). ## Symptom [Sekce “Symptom”](#symptom) V systému se objevují faktury na **0 Kč** u nových předplatných. ## Root cause [Sekce “Root cause”](#root-cause) Brána posílá `invoice.paid` i pro `billing_reason: subscription_create` (start trialu) s `amount_paid: 0`. Webhook na to bezpodmínečně vystavoval reálnou fakturu. ## Fix [Sekce “Fix”](#fix) Ve webhooku přeskoč nulové/zakládací události: ```js if (invoice.amount_paid === 0 || invoice.billing_reason === 'subscription_create') return; ``` ## Jak se tomu vyvarovat v jiných systémech [Sekce “Jak se tomu vyvarovat v jiných systémech”](#jak-se-tomu-vyvarovat-v-jiných-systémech) * **Detection:** seznam všech `billing_reason` hodnot, na které webhook reaguje. * **Anti-pattern:** „přišel paid event → vystav fakturu” bez rozlišení důvodu. * **Lepší přístup:** explicitní whitelist událostí, které znamenají reálné stržení. ## Sister bugs / související [Sekce “Sister bugs / související”](#sister-bugs--související) * [Přesun cesty k subscription po bumpu API](/payments/stripe-api-version-subscription-path-moved/). # Výkon > Pomalé dotazy, render a bundle — co degraduje až v měřítku. Bugy projevující se až při zátěži nebo větším objemu dat. ## Na co si dát pozor při implementaci [Sekce “Na co si dát pozor při implementaci”](#na-co-si-dát-pozor-při-implementaci) * Dotazy testuj na **produkčním objemu dat**, ne na 10 řádcích. * Pozor na **N+1** a neindexované filtry. * Sleduj růst **bundle size** a počet network requestů na klíčových stránkách. # Bezpečnost > Úniky dat, CSRF a oprávnění — co nesmí ven a co se nesmí obejít. Bugy v oblasti bezpečnosti, oprávnění a úniku dat. ## Na co si dát pozor při implementaci [Sekce “Na co si dát pozor při implementaci”](#na-co-si-dát-pozor-při-implementaci) * Veřejné výstupy (i tahle wiki) **nikdy neobsahují** IP, hesla, secrets, jména klientů. * Custom submit handler nesmí **zapomenout CSRF token**, který by jinak přidal nativní flow. * Oprávnění ověřuj **na serveru**, ne jen skrytím v UI.