Testovací provoz — web zatím není ostře spuštěn.

Changelog

Historie změn pro testery. Tato stránka bude odstraněna před produkčním spuštěním.

16. října 2026
  • Osobní údaje v auditu — pravidlo platí na všechny zápisy (AU-1c)
  • • **Kód**: ~20 míst (admin u makléřů, poptávek, spárování a hodnocení; vytvoření poptávky sellerem; opravená registrace makléře; hodnocení) zapisuje místo jmen a e-mailů ID, adresy a katastr vynechává. Audit úpravy katastru adminem (NF-20i) u čísla parcely, LV, č.p. a kódů zapisuje jen fakt změny (druh parcely s hodnotou); aktuální hodnota zůstává na poptávce. V přehledu auditu jsou jména dál vidět živě přes „Kontext entity“, dokud účet existuje.
  • • **Jeden seznam polí** `frontend/src/lib/utils/audit-pii-fields.ts` (přesná pole + přípony `_name`, `_email`, `_phone`, `_address`, `_street`).
  • • **DB pojistka** (migrace `20261016150000`): trigger `a_audit_logs_strip_pii` na `audit_logs` odstraní osobní údaje z každého zápisu bez ohledu na zdroj (kód, Edge Function, trigger, SQL) — záznam neodmítne, jen ho očistí. Seznam v DB hlídá test shody s aplikací. Migrace jednorázově vyčistila stávající záznamy (schválení/zamítnutí makléře, úpravy, vytvoření a smazání poptávky z testovacího provozu).
  • • **Statický test** `audit-pii-sources.test.ts` spadne na každém zápisu auditu v kódu, který použije klíč osobního údaje; `logAuditAction` a proxy `/api/audit-log` hlásí dynamické případy do Sentry.
  • Audit změn přes DB triggery (AU-1)
  • • **Trigger `zz_audit_row_change`** (funkce `audit_row_change`, migrace `20261016120000` + `20261016130000`) na `leads`/`bids`/`matches`/`profiles`: akce „Poptávka/Nabídka/Spárování vytvořeno/změněno/smazáno (DB)“, „Účet vytvořen / Profil změněn / Účet anonymizován – GDPR výmaz / Účet smazán natrvalo (DB)“. Obchodní údaje se starou a novou hodnotou (stavy, ceny, provize, faktura, plomba, katastrální území, parametry po klíčích), osobní údaje jen jako fakt změny (jméno, telefon, bankovní účet, DIČ, název firmy, adresy, GPS, číslo parcely, LV, popis poptávky, poznámky admina; fotky s počtem) — audit se nemaže a hodnota by přežila GDPR výmaz. Ověřovací kódy a tokeny nikdy. Technický šum (příznaky CRONu, `updated_at`) záznam nevytvoří; dlouhé texty se ořežou na 2000 znaků.
  • • **Autor**: přihlášený uživatel z JWT; u zápisů serveru servisním klíčem hlavičky `x-audit-actor`/`x-audit-source` (pomocník `auditHeaders`, dvě shodné kopie pro routy a Edge Functions) — trigger jim věří jen pod `service_role`, podvržená hlavička od uživatele se ignoruje (ověřeno sondou i testem). CRONy jako „Systém – expire-leads“ apod., registrace jako „Registrace“, serverová cesta bez hlaviček výrazně „server bez označení“.
  • • **Pole mazaná GDPR výmazem jen jako fakt** (migrace `20261016140000`, nález závěrečné review): `delete-account` u nespárovaných poptávek maže PSČ, katastrální území a kódy, u makléře vlastní název sítě a firmu mateřské pobočky — trigger je původně vedl s hodnotou, takže by výmaz přežily v auditu. Nově jen „změněno“; obec, kraj, okres a lokalita (výmaz je nemaže) zůstávají s hodnotou. Test „výmaz sellera s nespárovanou poptávkou“ RED→GREEN.
  • • **Ochrana značek triggeru**: proxy `/api/audit-log` odmítne metadata s `source_kind`/`actor_via`/`source` — jinak by klient vyrobil záznam, který se v přehledu tváří jako „Změny v DB“ / „Systém – …“ (nález závěrečné review).
  • • **Upozornění v přehledu auditu**, když je načtený jen posledních 1 000 záznamů (filtr a hledání starší neprohledají); filtrování na serveru → PRE-LAUNCH-TODO AU-1b.
  • • **Přísný režim**: zápis auditu je ve stejné transakci — když selže, neprovede se ani změna. Nouzový vypínač `ALTER TABLE public.<t> DISABLE TRIGGER zz_audit_row_change;`.
  • • **`audit_logs` jako neměnný důkaz** (migrace `20261016110000`): zrušen cizí klíč na `profiles` (`ON DELETE SET NULL` mazal autora ze všech záznamů tvrdě smazaného účtu — „makléř nahlásil prodej“ se měnilo na „systém“); `anon` a `authenticated` už nemají INSERT/UPDATE/DELETE/TRUNCATE (dřív je blokovala jen RLS, TRUNCATE RLS neřeší). Přehled auditu bere jména autorů z `get_users_with_email` (nasazeno dřív, `9a4ed16`).
  • • **`change_ico` v3**: metadata auditu bez jména makléře a názvu firmy (jen `broker_id`), notifikace adminům beze změny.
  • • **Serverové cesty a Edge Functions posílají autora**: `report-sale`, `confirm-contract`, `confirm-sale-seller`, `extend-lead`, `email-action` (uživatel z tokenu), `verify-email` (jen zdroj); `delete-account` kód v31 = platforma v33 (autor = volající), `send-verification-sms` v16, CRON `expire-leads` kód v10 = v15, `auto-approve-leads` v4 = v10, `watchdog-check` v8 = v22, `checkin-send` v13 = v18.
  • • **Vady stávajícího auditu (V1–V7)**: `logAuditAction` hlásí vrácené `{ error }` do konzole a Sentry (dřív selhání zmizelo), `email-action` loguje přes něj; klientský audit výběru makléře a duplikát hodnocení odstraněny (pokrývá trigger / server); pozastavení účtu a schválení/zamítnutí nahlášení prodeje zapisují skutečný předchozí stav; audit support ticketů bez e-mailu, předmětu a úryvku odpovědi; schválení/zamítnutí poptávky se loguje vždy; popisky „Potvrzení smlouvy / prodeje z e-mailu“. Proxy `/api/audit-log` odmítá akce triggeru i serverových rout (padělání).
  • • **Přehled auditu**: české popisky polí, „změněno (hodnota se neukládá)“, počty fotek, autor (přes server / Systém – zdroj / Registrace / server bez označení), filtr „Akce z aplikace / Změny v DB“.
15. října 2026
  • KRITICKÉ: makléři od 6. 7. nemohli podat nabídku
  • Odhalené nabídky zůstanou sellerovi vidět (B8)
  • • `leads.bids_revealed_at` — kdy se v tomto kole poprvé sešlo 5 nabídek; zapíše ho trigger semaforu při 5. nabídce (běží pod účtem makléře).
  • • Ochrana poptávky H-D v7 — seller si příznak nenastaví (obejití FOMO); ne-admin smí jen NULL → čas s důkazem, že nabídek je aspoň 5, čas ze serveru.
  • • Politika nabídek (H-E) bere příznak v úvahu; uvolněné místo se dál nabízí makléřům a nová nabídka se sellerovi ukáže rovnou.
  • • Zrušení spárování (M-6, nové kolo od 0/5) příznak vynuluje.
  • • UI sellera: sekce nabídek i stav karty („připraveno“) podle příznaku; text dialogu „Odmítnout“ v adminu opraven.
  • Soupis zápisů × audit log
  • Opravy nalezené při soupisu
  • • **Výmaz sellera neinformoval makléře** — Edge Function zamítla čekající nabídky ještě PŘED zrušením poptávek, trigger `handle_lead_cancelled` (FL-M8) pak žádnou nenašel a makléři se o zrušení nedozvěděli. Nově nabídky zamítá trigger i s in-app „Poptávka byla zrušena“; počet zamítnutých nabídek v auditu = otevřené nabídky na skutečně zrušených poptávkách. `delete-account` kód v29 = platforma v31.
  • • **Výmaz makléře** zamítá i nabídky ve stavu `seen` (stejná množina jako trigger a `create_match`; stav dnes nic nenastavuje — konzistence).
  • • **Expirace poptávky** (`expire-leads` kód v9 = platforma v14) — přepne jen poptávku, která je pořád `active` a po termínu (dřív jen podle ID: poptávka spárovaná, zrušená nebo prodloužená během běhu CRONu by skončila jako vypršelá, service_role obchází H-D trigger); 0 řádků = bez e-mailu „vypršela“.
  • • **Automatické schválení** (`auto-approve-leads` kód v3 = platforma v9) — notifikace sellerovi jen když se poptávka opravdu schválila (optimistic lock vracel 0 řádků bez chyby → falešné „schválena“ po zamítnutí adminem).
  • • **Admin a stav nabídky** — select stavu (Čekající/Přijatá/Odmítnutá) dovoloval přepnout i spárovanou nabídku nebo „přijmout“ bez spárování. Nově štítek stavu + tlačítko „Odmítnout“ jen u čekajících nabídek poptávky bez běžícího spárování (`lib/utils/can-admin-reject-bid.ts`), s kontrolou počtu změněných řádků; dialog upozorní, že odmítnutí 5. nabídky skryje sellerovi odhalené nabídky.
  • • **E-maily podpory** — potvrzení dotazu a odpověď admina šly přes Resend přímo bez pojistky proti nedoručitelným adresám (testovací `.test`, smazané účty). Kopie `isUndeliverableEmail` ve `frontend/src/lib/utils/undeliverable-email.ts` (Coolify staví jen `frontend/`) + test shody obou kopií; e-mail na info@ jde vždy.
  • • **IP při výmazu účtu (B7)** — ověřeno na živé Edge Function: brána Supabase neposílá `x-real-ip` a `x-forwarded-for` přichází jako „klient,klient, proxy AWS“ (proxy `13.248.119.x` se mění); hlavičky podvržené klientem brána zahodí. Limit 5 pokusů/h bral POSLEDNÍ položku = IP proxy (všichni uživatelé sdíleli pár kbelíků — cizí pokusy mohly zablokovat poctivého uživatele, útočník rotoval kbelíky) a snapshot ukládal celý řetězec, v admin režimu IP admina. Nově první položka po validaci (`_shared/client-ip.ts`); výmaz adminem ukládá do snapshotu NULL (kdo výmaz provedl, nese `audit_logs`). `delete-account` kód v30 = platforma v32. Dva starší snapshoty z 2026-10-07 nechávám beze změny (doklady).
  • • **Pojistka znění podmínek** — souhlas se ukládá jako číslo verze; test `terms-version-guard.test.ts` hlídá otisk viditelného textu `/terms` a `/terms/broker` pro aktuální verzi (CSS třídy se nepočítají). Změna slov bez nové verze = test spadne. Otisky verze 0.6 odpovídají commitu `0ce9a16` (stránky od té doby beze změny).
9. října 2026
  • Audit smazání poptávky adminem
  • Drobnosti z review watchdogu (NF-20)
  • • **Rychlá změna typu nemovitosti** (a) — výsledek dohledání katastru se použije jen z posledního dotazu (`lib/utils/latest-request.ts`); když dohledání po změně typu selže, katastrální údaje se vymažou (admin doplní, watchdog ohlásí chybějící údaj) místo parcely pro předchozí typ.
  • • **Mapa** (b) — Leaflet volá vždy aktuální handler přes ref (dřív si držel typ nemovitosti z chvíle otevření mapy).
  • • **Číslo evidenční** (c) — našeptávač vrací „Staré Splavy ev. č. 498“ a formulář z toho dělal č.p. 498 (u bytu by watchdog hledal jednotku v jiném domě). Nově se ukládá „ev. 498“ (`lib/utils/house-number-from-address-name.ts`), budova s číslem evidenčním z RÚIAN (`typstavebnihoobjektukod = 2`) taky; watchdog podle něj jednotku nehledá (byty v budovách s č.ev. jsou mimo rozsah — admin vidí upozornění).
  • • **Budova bez parcely nebo bez části obce** (d) — když RÚIAN identifikační parcelu budovy nevrátí, vezme se nouzově parcela pod bodem; budova bez kódu části obce si parcelu nechá.
  • • **Detail spárování** (e) — „Watchdog hlídá:“ jen u spárování, které watchdog kontroluje (`lib/utils/is-watchdog-watching.ts`, stejná podmínka jako výběr v Edge Function), ne u prodaných a zrušených.
  • • **Pole čísla jednotky** (f) — opuštění pole beze změny vstupů katastr znovu nedohledává (`lib/utils/unit-lookup-key.ts`; limit 10 dotazů/min a 1000/den klíče ČÚZK).
  • • **Pojistka v dotazech RÚIAN** (g) — ID parcely, kód k.ú. a kód budovy z odpovědí RÚIAN jdou do dotazu jen jako celá čísla.
  • • **Živý test stavební parcely** (h) — st. 5901/1 (k.ú. 602868) jako stavební → LV 4144, jako pozemková „nenalezeno“ (přesně původní chyba NF-16); živý test č.ev. 498.
  • • **Audit úprav katastru adminem** (i) — nová akce `edit_lead_cadastral` s původními a novými hodnotami (`lib/utils/cadastral-audit-changes.ts`) + město, typ, prodávající (self-contained důkaz).
  • Watchdog hlídá správný objekt v katastru (NF-16)
  • • **Byty** — prodej bytové jednotky se na parcele pod domem ani na budově neprojeví. Praha 3, č.p. 2938: jednotka 112 má plomby (vklady V-62741/2026, V-63385/2026), parcela 1928 pod domem i budova 0 plomb. Watchdog číslo jednotky vůbec nepoužíval → prodej bytu by nezachytil nikdy.
  • • **Stavební parcely** — v k.ú. se dvěma číselnými řadami („st. 123" vs. „123") kontroloval cizí parcelu; poptávka druh číslování neukládala.
  • • **Vchod mimo budovu** — parcela se při zadání poptávky brala pod GPS bodem adresy. Kaplířova 2938/3 (stejný dům) → bod na chodníku, parcela 1932 a cizí LV 2064, LV bytu se nenašlo.
  • • **Řízení** — watchdog četl neexistující pole odpovědi; plombu poznal, ale admin neviděl typ ani číslo řízení.
  • • **Pravidlo „co hlídat"** `resolveWatchTarget` — dvě znak po znaku shodné kopie (`supabase/functions/_shared/watch-target.ts` pro watchdog, `frontend/src/lib/utils/watch-target.ts` pro admin UI, test hlídá shodu): s číslem jednotky → jednotka (část obce + č.p. + číslo jednotky), byt bez čísla jednotky → nehlídá nic (ne parcelu pod domem — falešný pocit hlídání), jinak parcela se skutečným druhem číslování. Chybějící údaj = konkrétní důvod.
  • • **`_shared/cuzk-plomba.ts`** (`checkPlomba`) — `Jednotky/Vyhledani` nebo `Parcely/Vyhledani` s druhem číslování; řízení čitelně „V-62741/2026-101" (formát Nahlížení do KN), typ „Vklad / Záznam / …"; nenalezený objekt = chyba (chybný údaj v poptávce), ne „bez plomby".
  • • **`watchdog-check` v7** (platforma v21) — hlídá cíl z pravidla; chybí-li údaj, match přeskočí s důvodem (čas poslední kontroly se neposune); in-app upozornění adminovi nese řízení. Změnil se formát `plomba_rizeni_id` (dedup M-3) — dnes žádné matche, bez dopadu.
  • • **`/api/cadastral-lookup`** — u staveb parcela POD BUDOVOU: RÚIAN adresní místo podle č.p./č.o. v okruhu 60 m → stavební objekt → identifikační parcela + kód části obce; bez adresy (mapa) budova pod bodem; pozemek vždy parcela pod bodem. k.ú. podle parcely (bod u hranice k.ú.). LV jednotky s kódem části obce z budovy (dřív chyběl → HTTP 400), odstraněn neplatný parametr `KodKatastralnihoUzemi`. Nové moduly `lib/cadastral/` (`parse-house-number`, `ruian-query`, `resolve-building`, `fetch-cadastral`, `types`).
  • • **Formulář nové poptávky** — ukládá `parcel_type` a `municipality_part_code`; výběr na mapě doplní č.p. budovy; změna typu nemovitosti po zadání lokace (pozemek ↔ stavba) katastrální údaje znovu dohledá; zobrazuje „st. 123"; kontrola duplicity porovnává i k.ú. a druh parcely.
  • • **Admin** — detail poptávky: „Watchdog bude hlídat: jednotku 2938/112 (k.ú. Žižkov)" nebo upozornění s důvodem už při schvalování; editace katastru má typ parcely a č.p. (validace: číslo bez „st.", číslo vyžaduje typ). Seznam spárování ukazuje konkrétní důvod, proč watchdog nehlídá (i u `contracted`); detail spárování „Watchdog hlídá:" a „Řízení: Vklad V-…".
  • • **DB** — migrace `20261009180000`: `leads.parcel_type` (1 stavební / 2 pozemková, CHECK) + `leads.municipality_part_code`; zámek H-D v6 (oba sloupce se po schválení pinují jako číslo parcely); backfill poptávky Chrást (526/8, druh 2, část obce 53783). Definice triggeru převzata ze živé DB (diff 0), dry-run s ASSERTy + negativní kontrolou, granty beze změny, FL-K1 výjimka zachována.
  • • **`delete-account` v28** (platforma v30) — anonymizace nespárovaných poptávek nuluje i nové sloupce.
  • Úklid fotek, které nepatří k žádné poptávce (NF-15, NF-18)
  • • **DB funkce `list_orphan_lead_photos`** (migrace `20261009120000`, SECURITY DEFINER, jen service_role — anon/authenticated bez EXECUTE): soubory v bucketu, na které neodkazuje žádné `leads.photos_interior/exterior` (poptávka v jakémkoli stavu, URL porovnávaná podle části za `/property-photos/` bez query stringu). Volitelný prefix složky a minimální stáří.
  • • **`_shared/orphan-photos.ts`** (`removeOrphanLeadPhotos`) — přečte osiřelé soubory a smaže je přes Storage API po dávkách 100.
  • • **`delete-account` v27** (platforma v29) — nový krok 8f smaže osiřelé fotky ve složce mazaného uživatele (bez věkového limitu); fotky spárovaných poptávek zůstávají (důkaz). Počet v auditu `delete_user` a odpovědi (`orphan_photos_deleted`). Best-effort — chyba výmaz nezastaví, zbytek dožene denní úklid.
  • • **`expire-leads` v8** (platforma v13) — fáze D: denně smaže osiřelé fotky starší 7 dní (rezerva pro rozpracovaný formulář). Odpověď CRONu nese `orphan_photos_removed`.
  • • **`photo-upload.tsx`** — „Odebrat" se o smazání ze Storage pokusí jen u fotky nahrané v tomtéž formuláři (pojistka pro případ budoucí SELECT policy; dnes no-op), uloženou fotku jen vyřadí z formuláře. Odebrané soubory uklidí fáze D po 7 dnech.
  • Poptávka: texty pro makléře a admina, vodoznak fotek, katastr (nálezy E2E testu)
  • • **Lokalita „Chrást - Chrást"** — našeptávač Mapy.cz (i výběr na mapě) vrací u obcí bez dalších částí část obce pojmenovanou stejně jako obec a formulář je spojil bez kontroly → makléři by v e-mailu i zvonečku dostali „Nová poptávka v Chrást - Chrást". Nový pomocník `lib/utils/location-area.ts` (`buildLocationArea`) připojí část obce jen, když se od obce liší (porovnání bez ohledu na velikost písmen a mezery); použit v obou větvích `step-2-location.tsx`. 3 unit testy. Testovací poptávka opravena v DB.
  • • **„dum" místo „Dům"** — upozornění adminovi („Petr Nosek - dum v Chrást, …") i e-maily `new_lead` / `lead_pending_review` („Typ nemovitosti: dum") ukazovaly interní kód typu nemovitosti. Migrace `20261009094000`: `notify_admin_pending_review` píše český popisek a lokalitu odděluje čárkou („Petr Nosek - dům, Chrást, okres Plzeň-město"; dry-run s rollbackem, granty beze změny). `send-notification-email` v29 (platforma v49): `property-type.ts` (`propertyTypeLabel`) v šablonách lead; test hlídá shodu popisků s webem (`PROPERTY_TYPE_LABELS`).
  • • **Vodoznak byl skoro neviditelný** — logo mělo zabírat polovinu šířky fotky, ve skutečnosti zabíralo asi osminu: `public/watermark-logo.svg` má artboard 450×253 a logo v něm jen ~134×40 (zbytek prázdný okraj). viewBox oříznut těsně na logo (146×52 s okrajem pro stín), fallback poměr stran v `watermark.ts` upraven. Týká se nově nahraných fotek; náhled ověřen vykreslením na testovacích fotkách.
  • • **Číslo LV se nedohledávalo** — klíč ČÚZK REST API KN vypršel 3. 8. 2026; `cadastral-lookup` chybu jen zalogoval. Nový klíč nastaven v Coolify, Supabase secrets (watchdog) i lokálně, produkce ověřena (parcela 526/8 → LV 1439). Hlídání vypršení + chybových stavů → PRE-LAUNCH-TODO NF-17.
8. října 2026
  • Časy událostí podle serveru, ne prohlížeče (NF-13)
  • Zvoneček po schválení makléře hned ukazuje nové upozornění
  • Nasazení padalo od 7. 10. — build bez testů
  • Profil makléře: IČO nejde smazat
  • Formuláře: pole s chybou už nezakrývá horní menu
7. října 2026
  • E-maily se neposílají na nedoručitelné adresy (vyčerpaný limit Resendu)
  • Administrace: nabídku ani poptávku se spárováním nelze smazat
  • Podmínky spárované nabídky nejdou přepsat (NF-11)
  • Výmaz účtu: archiv před jakýmkoli mazáním (delete-account v26, NF-9)
  • Dialog výmazu účtu pro přihlášené přes Google (NF-10)
  • Výmaz účtu bez archivu nedoběhne (delete-account v25)
  • Údaje uložené u spárování nejdou přepsat (právní důkaz)
  • Dev: Next.js negeneruje AGENTS.md / CLAUDE.md
  • Chyba formuláře je vidět, i když je uživatel dole u tlačítka
  • Doklad souhlasu s podmínkami přežije výmaz i novou verzi podmínek + dořešení výmazu
  • • Migrace `20261007120139`: `matches.broker_terms_version_snapshot` + `broker_terms_accepted_at_snapshot` — `create_match` ukládá souhlas makléře platný v okamžiku spárování (pořadí FL-K1 a guardy beze změny, diff funkce = jen 3 snapshot změny, granty ověřeny); `account_deletion_audit.terms_version` + `terms_accepted_at` — souhlas v okamžiku výmazu; defenzivní `REVOKE` na `scrub_auth_identities`. Dry-run s asserty + negativní kontrola
  • • `delete-account` **v23/v24**: snapshot nese souhlas; napůl doběhlý výmaz (profil smazaný, ale selhal scrub/ban) jde zopakovat opravnou větví (audit `repair`, bez nového snapshotu) místo slepého 409; selhání scrub/ban → `500 PARTIAL_DELETE` místo tichého 200; ověřovací session se hned odhlásí; v admin režimu se e-mail admina nikdy nepoužije jako e-mail cíle (nálezy finálního review). **v24** (druhé review): kompletnost výmazu zahrnuje i prázdná metadata — když selže jen scrub (ban prošel), opakování jde opravnou větví místo tichého 409
  • • Testy: match-lifecycle (snapshot souhlasu, makléř bez souhlasu), delete-account D (authenticated nesmí `scrub_auth_identities`) + E + F (opravná větev při selhání banu / jen scrubu), anon-execute + `scrub_auth_identities`; sweep i osiřelých snapshotů — 71 integračních testů (lifecycle + security 57/57 zelené)
  • GDPR výmaz účtu: dočištění osobních údajů, oprava mazání z profilu, audit
  • • **Osobní údaje zůstávaly v `auth.users.raw_user_meta_data`** — registrační data (jméno, telefon, IČO, číslo účtu, adresa sídla/kanceláře, původní e-mail). EF posílala `user_metadata: {}`, ale GoTrue metadata slučuje → no-op. Migrace `20261007114103`: `scrub_auth_identities` v2 vynuluje metadata a smaže `auth.sessions` (IP + user agent; CASCADE refresh tokeny) + backfill smazaných účtů. Dry-run: asserty na staré funkci selhaly (reprodukce), na nové prošly; granty beze změny, regrese `anon-execute` zelená
  • • **Mazání účtu z profilu (heslem) tiše nedoběhlo** — EF ověřovala heslo `signInWithPassword` na service klientovi, supabase-js session uložil a další dotazy šly pod JWT uživatele: odpověď 200, ale `deleted_at` zůstal prázdný (C2 trigger), nevznikl snapshot ani scrub. **v22**: heslo ověřuje samostatný jednorázový klient. V DB žádný takto napůl smazaný účet nebyl
  • • **Výmaz nezapisoval audit** — v22 zapisuje `audit_logs` `delete_user` (kdo, koho, režim admin/self, odkaz na snapshot, bez PII); proxy `/api/audit-log` akci odmítá (jen server)
  • • **Opakovaný výmaz** už smazaného účtu → 409 `ALREADY_DELETED` (dřív druhý snapshot s anonymizovanými daty)
  • • Regresní integrační test `security/delete-account.test.ts` na živé EF (AAL1 admin 403, admin výmaz + opakování, self-service heslem) — 66 integračních testů; unit 327
  • Admin mazání uživatele opět funguje
  • • **Edge Function `delete-account` odmítala každé admin mazání (403 `NOT_ADMIN`)** — od FL-I (migrace `20260705020900`, 5. 7.) vyžaduje `is_admin()` kromě `role='admin'` i JWT claim `aal='aal2'` z `auth.jwt()`. Funkce ale `is_admin` volala **service_role klientem** → v DB nebyl JWT volajícího admina, claim chyběl → vždy `false`. Ověřeno na živé DB (`is_admin` pod service_role kontextem `false`, pod admin AAL2 JWT `true`). Self-service výmaz z profilu tím dotčen nebyl (jiná větev). **v21**: RPC `is_admin` jde klientem s `Authorization` volajícího → role i AAL2 se vyhodnotí v DB (jediný zdroj pravdy), AAL1 admin token dál neprojde. Nasazeno, smoke test 401 bez přihlášení / s anon klíčem
  • • **Chyba nebyla vidět**: Radix `AlertDialogAction` po kliku dialog hned zavřel a `onOpenChange` vynuloval `deleteError` → hláška z Edge Function se nikde nezobrazila. Nyní `preventDefault` — dialog zůstává otevřený s „Mažu…", chyba se zobrazí v něm (`section-header.tsx`)
  • • Build ✓
  • Smazaný makléř už nedostává e-maily o nových poptávkách
  • • Migrace `20261007101310`: filtr `deleted_at IS NULL` ve výběru příjemců všech tří funkcí (sjednoceno se vzorem `notify_admin_ico_change` v2); těla jinak beze změny (převzata z živé DB), `CREATE OR REPLACE` → vlastník i granty zachovány (EXECUTE jen postgres + service_role)
  • • **Ověřeno** dry-runem s rollbackem na živé DB: fáze 1 se starými funkcemi chybu reprodukovala (smazaný makléř dostal `new_lead`), fáze 2 s novými: smazaný makléř 0 / aktivní 1, nesmazaný admin 1 / smazaný admin 0 u obou admin broadcastů, granty beze změny; negativní kontrola (záměrně chybný ASSERT) spadla → asserty se vyhodnocují. Po nasazení: filtr ve všech třech funkcích, ACL shodné, regrese `anon-execute` 19/19
  • • Kontext: příprava ručního E2E testu celého flow — založeno 5 testovacích makléřů (Gmail aliasy, lokální `TEST-ACCOUNTS.md` mimo git)
  • • Nový dokument `TEST-FLOW-NOTIFICATIONS.md`: mapa všech e-mail/in-app notifikací celého flow, CRONy a SQL recepty pro ruční spuštění a posun času. Další nálezy mapování (NF-1..NF-5: admin o nové poptávce jen in-app, 5s timeout pg_net u CRON volání, 1-click nekonzistence, …) zapsány do `PRE-LAUNCH-TODO.md` sekce 4c; DP-12 vyřešen v DB části
  • Bezpečnostní upgrade Next.js 16.2.9 → 16.3.8
  • • `next` `^16.2.9` → `~16.3.8` (tilde = jen patche 16.3.x, minor upgrade bude vědomé rozhodnutí); s ním `sharp` 0.35.5, bundled `postcss` 8.5.23
  • • `npm audit fix --omit=dev` (produkční strom) + cílený `npm update` dev nástrojů (`js-yaml`, `lint-staged` 16.4.0, `@humanfs/node`, `brace-expansion`); `vitest` + `@vitest/coverage-v8` 4.1.8 → 4.1.11 (path traversal v `@vitest/mocker`)
  • • `eslint-config-next` **záměrně ponechán na 16.1.6**: novější verze táhne `eslint-plugin-react-hooks` 7.1.1 s přísnějšími React Compiler pravidly → 16 nových lint errors v existujícím kódu (`set-state-in-effect` ×10, `immutability` ×3, `purity`, `refs`, `preserve-manual-memoization` — mj. `AuthContext`). Kód se nezměnil, změnil se linter; úklid jako samostatný úkol (PRE-LAUNCH-TODO sekce 5)
  • • **Výsledek**: `npm audit --omit=dev` = **0 zranitelností** (předtím 1 critical + 7 high). Zbývá 5 high jen v dev ESLint řetězci (`braces` ← `micromatch` ← `fast-glob` ← `@next/eslint-plugin-next`) — opravená verze `braces` zatím neexistuje, týká se jen lintování
  • • **Ověřeno**: eslint (0 errors, stav shodný s HEAD — změřeno na čisté kopii), tsc, 326 testů, `next build --webpack`, smoke test produkčního serveru (`next start`, jako Nixpacks): 10 stránek vč. 404, security hlavičky, statické chunky, API routes (`email-action` 404, `lead-photos`/`extend-lead` 401 bez přihlášení, `check-availability` 200 přes DB). `address-suggest` lokálně 403 od Mapy.cz — klíč v `.env.local` je z lokální IP blokovaný i přímým curlem mimo Next (není regrese; produkce vrací návrhy)
18. srpna 2026
  • Lišta „Testovací provoz" na všech stránkách
29. července 2026
  • Změna IČO makléře po schválení — audit + notifikace adminům
  • Pětiúhlá code review IČO triggeru — v2 + 3 FE opravy
  • Ověření end-to-end z testování (fronta z 28. 7.)
  • • **E-mail „Váš účet byl schválen"** po schválení makléře dorazil hned — fix `SUPABASE_SECRET_KEY` potvrzen v produkci.
  • • **Resubmit flow (matice 1.2.9, 1.2.10) kompletně ověřen**: zamítnutí → e-mail s důvodem → makléř vidí důvod → oprava na `/auth/resubmit` (změna adresy) → DB správně (`pending_approval`, vynulovaná metadata zamítnutí) → admin notifikace „Makléř upravil registraci — ke schválení" → schválení → e-mail + automatický přechod čekací stránky na dashboard.
  • • **AbortError po upgradu supabase-js zmizely**: od deploye žádný event ani při intenzivním auth testování → Sentry issues JAVASCRIPT-NEXTJS-3/4 resolved (regrese je auto-reopne), DP-9 v PRE-LAUNCH-TODO uzavřen celý (bod b — custom auth lock — se nerealizuje, podmínka recidivy nenastala).
28. července 2026
  • Upgrade supabase-js 2.91.1 → 2.110.9 — skutečná příčina AbortError incidentů (osiřelé Web Locks)
  • KRITICKÝ FIX — e-maily ze server routes se tiše neodesílaly (špatný klíč pro Edge Functions)
  • Code review registračních commitů (26824bb + ecbcf86) — 4 opravy
  • Code review resubmit flow — 7 oprav před nasazením
  • Broker resubmit flow + admin editace údajů makléře
  • • **Migrace `20260728131138`**: C2 trigger `protect_profile_privileged_columns` v2 — resubmit výjimka (vlastník smí JEN `rejected → pending_approval`, approval metadata se přitom vynulují — vzor lead resubmitu H-D v5) + zpřísnění (approval metadata nově zamčena i běžným uživatelům). `notify_admin_pending_broker` rozlišuje novou registraci vs. resubmit („Makléř upravil registraci — ke schválení"), nový trigger `on_broker_resubmit_notify_admin`. Dry-run 5 scénářů s rollbackem na živé DB, poté nasazeno; +4 integrační testy (`broker-resubmit.test.ts`, 56 celkem).
  • • **`/auth/resubmit`**: zamítnutý makléř vidí důvod zamítnutí a upraví registrační údaje (znovupoužité sekce registračního formuláře vč. ARES ověření IČO při změně a Mapy.cz našeptávače); e-mail a telefon se nemění (ověřené identifikátory). Tlačítko „Upravit údaje a odeslat znovu" na stránce zamítnutí. Optimistic lock na status. Audit `resubmit_broker_registration`.
  • • **Admin editace makléře**: karta „Firma a kancelář" → „Upravit" — jméno, firma, sídlo, DIČ, účet, kancelář (autocomplete + reset při ruční editaci), síť. E-mail/telefon/IČO záměrně editovat nelze. Audit `edit_broker_profile`.
  • DP-9 — klasifikace selhání profilu: konkrétní návod + self-healing session
  • • **`classifyProfileFetchError`** (`lib/utils/profile-retry.ts`): „abort" (zamrzlý zámek — retry zkrácen na 2 pokusy, nepomáhá) / „session" (PGRST301/302, „JWT", PGRST116 po retry = smazaný účet) / „generic" (výpadek API — beze změny).
  • • **Session self-healing**: mrtvá session → toast „Vaše přihlášení už není platné" + automatické odhlášení → login (dřív slepá ulička na chybové stránce). Kryje i GDPR výmaz účtu z jiného zařízení.
  • • **`/auth/profile-error`** při „abort" radí konkrétně: zavřít ostatní záložky webu a restartovat prohlížeč (reload nepomáhá). Nový context field `profileErrorReason` (aditivní — `profileError` boolean pro stávající konzumenty beze změny).
  • • Sentry capture nově nese klasifikaci. +7 testů (325 celkem). Zbývá z DP-9 jen případná custom lock konfigurace supabase klienta (post-launch, pokud se incident bude opakovat).
  • Fix — přechodný výpadek API už neposílá uživatele na chybovou stránku profilu
27. července 2026
  • Audit úplnosti ukládání formulářů + 4 opravy
  • Fix — broker registrace ztrácela adresu kanceláře a další data + admin je nevidí
  • • **Migrace `20260727182250`**: `handle_new_user` přenáší všechna registrační metadata (NULLIF-safe casty pro GPS/uuid/boolean; seller beze změny — klíče neposílá). Součástí **backfill** čerstvě registrovaného brokera z jeho metadat. Dry-run v transakci: simulovaná broker i seller registrace přímým INSERT do `auth.users` + ASSERTy + rollback; poté nasazeno a ověřeno na reálném brokerovi.
  • • **Admin detail uživatele**: nová karta **„Firma a kancelář"** (broker) — sídlo firmy, DIČ, bankovní účet, adresa kanceláře s odkazem na mapu, členství v realitní síti (vč. custom sítě „ke schválení" a mateřské pobočky), souhlas s podmínkami (verze + datum). Hook dotahuje název sítě z číselníku `networks`.
  • UX fix — zavádějící „Číst celé znění smlouvy" v broker registraci
  • Registrace hlásí duplicitní e-mail i telefon najednou
  • • **Migrace `20260727150338`**: nová funkce `email_exists(p_email)` — SECURITY DEFINER `EXISTS` nad `auth.users` (case-insensitive, `deleted_at IS NULL`), granty podle lockdown vzoru **service_role-only** (žádný nový anon enumerační povrch). Ověřeno dry-run transakcí s ASSERT testy + rollback, poté nasazeno (`db query` + `migration repair --status applied`; `db push` nelze — vzdálená historie obsahuje base migrace mimo repo). Živě ověřeny granty i chování; anon-execute integrační regrese (18 testů) prošla.
  • • **`/api/check-availability`**: přijímá `{ phone?, email? }` (aspoň jedno), vrací `{ phoneAvailable?, emailAvailable? }`; oba checky paralelně; validace e-mailu (formát + 254 znaků); rate limit 10/min beze změny. E-mail check neprozrazuje nic nového — stejnou informaci vrací chyba `signUp`.
  • • **Oba registrační hooky** (seller + broker): posílají telefon i e-mail v jednom requestu, hláška podle kombinace — „Tento email i telefon jsou již registrovány" / jen e-mail / jen telefon. Bonus: duplicitní samotný e-mail se nově chytí ještě před `signUp`.
  • • Testy route přepsány a rozšířeny 8 → 16 (305 celkem). Reálný smoke test route proti živé DB (dočasný dev server): obsazený e-mail + volný telefon, volný e-mail, 400 bez parametrů — vše správně.
  • UI fix — registrace nalepená na horní menu
  • UI fix — rozházený text souhlasu v registraci sellera
7. července 2026
  • Integrační testovací sada (Fáze 1) — automatizované testy před manuálním testováním
  • • **A — Bezpečnost/RLS:** anon-execute (regrese anon-EXECUTE fixu), anon-read (C3/FL-H1 anonymizace), C2 eskalace (role/status pin, cross-user notif), AAL2 admin (TOTP přes `otplib` — admin bez MFA nemá práva).
  • • **B — Lifecycle:** create_match FL-K1 end-to-end + reveal, guardy (ownership/rejected/neaktivní/dvojí přijetí), FOMO reveal, M-6 cancel→reopen, FL-I sync sold/revert, lead cancel FL-M8.
  • • **C — API routes:** extend-lead (Bearer, ownership, kotva), confirm-contract (→contracted + protistrana), lead-photos G-8 autorizační matice (seller/broker-match/admin-AAL2 → vše; broker bez matche → jen interiér; neaktivní/AAL1 → 403). report-sale + confirm-sale-seller guarded (notifikují reálné adminy → běží jen bez reálného admina).
  • • **D — CRON:** expire-leads auth-gate (401) + efektový test (fáze B expirace, guarded na reálná data, `SUPABASE_CRON_KEY`).
6. července 2026
  • Bezpečnostní oprava — anon EXECUTE na citlivých SECURITY DEFINER funkcích (automated-testing sweep)
  • • **#1 HIGH — `get_user_email_by_id`**: anon vytáhl **e-mail libovolného uživatele podle UUID** (GDPR únik). Ověřeno reálným HTTP: `POST /rest/v1/rpc/get_user_email_by_id` s anon klíčem → před opravou vrátilo e-mail, po opravě `401 / 42501 permission denied`. Fix: `REVOKE ... FROM public, anon`; ponechán `authenticated` (admin volá client-side, guard `is_admin`) + `service_role`.
  • • **#2 MEDIUM — `create_notification`**: anon injektoval in-app notifikaci komukoli (phishing `title/body/href`). Ověřeno `SET ROLE anon` (rollback) → vrácen UUID. Fix stejný (guard cross-user chrání `authenticated`).
  • • **#3 MEDIUM — `get_bidding_broker_ids` / `get_broker_bid_lead_ids` / `get_matched_seller_ids` / `get_seller_lead_ids`**: bezguardové orphaned DEFINER helpery → enumerace/deanonymizace makléřů/leadů/matchů podle UUID (obchází model anonymního kruhu). Nevolají se z app kódu, RLS policies ani views (ověřeno) → `REVOKE ... FROM public, anon, authenticated`, zůstává jen `service_role`.
  • FL-L/FL-I závěrečná dávka — flow audit KOMPLETNĚ UZAVŘEN
  • • **„Moje nabídky" s kontextem i pro pending/rejected bidy**: nové snapshot sloupce na `bids` (`lead_location_area_snapshot`, `lead_property_type_snapshot`, `lead_params_snapshot {size_m2, rooms}`) plněné BEFORE INSERT triggerem `snapshot_bid_lead_context` (SECURITY DEFINER — čte VŽDY z leads, klientský podvrh přepíše) + backfill. Dřív RLS (`leads_select` jen matchnuté) + view (jen aktivní neobsazené) → „Poptávka nedostupná" přesně u pending bidů na plných leadech. `exact_address` dál jen u accepted (base join přes match). Jen anonymizovaná pole, která broker viděl při podání — žádný nový únik; GDPR-imunní (vzor `matches.*_snapshot`).
  • • **`price_label` už neodchází brokerovi v payloadu**: `use-broker-lead-detail` + `broker/bids` z `select("*")` na explicitní column listy (cenový semafor je seller-only; UI ho nerenderovalo, ale network response ano).
  • • **„Makléř #N" čísluje podle příchodu**: bidy řazeny ASC (dřív DESC → #1 = nejnovější; kolečka slotů implikují pořadí příchodu).
  • • **Admin matches — mapa vs. handlery sjednoceny**: `sold→watching` doplněn do VALID_TRANSITIONS (revert plomba false-positive, zrcadlí PŘESNĚ nulování z detail handleru); inline `contracted→watching` nuluje contract timestampy (jinak duplicate guard 400 při re-potvrzení); **`invoiced→cancelled` ODEBRÁN** (rozhodnutí — po faktuře jen →paid, cancel nechával evidovanou fakturu a neotevřel lead) vč. skrytí tlačítka Zrušit na detailu; **inline `cancelled` odfiltrován ze selectu** (cancel jen přes detail — dialog s důvodem + `watchdog_active=false`); lokální sync stavu přepisuje pole VČETNĚ null (dřívější `??` null tiše spolkl).
  • • **submit-rating deep-link**: fallback `rating_id=match_id` odstraněn (rozbíjel auto-expand na /admin/ratings).
  • • **Zvoneček a /notifications sdílí state** (rozhodnutí: bez Realtime): nový `NotificationsProvider` (jeden 60s polling pro celou app + refetch při fokusu okna s 5s throttle); přečtení na stránce se na badge projeví okamžitě; `useNotifications` je nyní context consumer.
  • • **Fáze B check-in = plný dual-write** (rozhodnutí): strany dostávají e-mail + in-app (`contract_reminder`, gate `cooperation/inapp` — vzor fáze A), admini in-app + NOVĚ e-mail (šablona `contract_unsigned`, bez pref gate).
  • • **NOTIFICATION_CONFIG synchronizován s realitou**: 5 mrtvých klíčů pryč (`match_seller/broker`, `payment_reminder`, `checkin_request/reminder`), ~12 reálných typů doplněno, mrtvé pole `icon` odstraněno; drift guard test.
  • • OAuth-only uživatel: karta „Změna hesla" nahrazena info boxem (dialog vždy selhal na ověření aktuálního hesla).
  • • `verify-email` rozlišuje výsledky: expirovaný token → `expired_token` (hláška byla dřív nedosažitelná), už ověřený → friendly `email-verified?already=1`; copy `invalid_token` kryje i použitý odkaz (úspěch token nuluje).
  • • Chybové query params se konečně renderují: login zobrazí `?reason=mfa_check_failed` i `?error=oauth_failed/missing_code` (Suspense refaktor), homepage `/?deleted=1` → toast potvrzení smazání účtu (`DeletedAccountNotice`).
  • • `/auth/suspended` SMAZÁNA (0 referencí — read-only přístup s bannerem na dashboardu je záměr); `/auth/pending-approval` už nerenderuje kartu nepřihlášenému (redirect login/profile-error/loader); `termsOutdated` mrtvé API odstraněno z useAuthGuard; `requireMfa` latentní dead-end pro non-admin opraven (mfaChecked defensivně); text na verify stránce pravdivý (mažou se jen účty bez jediného ověření).
  • • **Resubmit čistí review metadata** (H-D trigger **v5**): `rejected→pending_review` nuluje `reviewed_by/reviewed_at` (dřív držely zamítajícího admina a čas rejectu do dalšího schválení).
  • • **Cleanup rejected leadů** (rozhodnutí: 30 dní): nová fáze C v expire-leads (`cleanup-rejected.ts`) — rejected lead 30+ dní od zamítnutí (guard i na `updated_at` — aktivně editovaný draft se nemaže) → smazání fotek přes Storage API (vzor delete-account, fotky PŘED řádkem) + DELETE řádku. Rejected lead nikdy nebyl aktivní → bez bidů/matchů.
  • • `cleanup_unverified_accounts` **v3**: odstraněn mrtvý soft-delete UPDATE blok (hard DELETE + FK CASCADE ho činil no-op), harden search_path; chování beze změny.
  • • Docs drift: DATABASE.md notifications RLS opraveno (žádná `insert_authenticated` policy neexistuje).
5. července 2026
  • FL-I dávka 3: lead sold sync, připomínka sale_reported, duplicitní lead, cancel pending_review
  • • **`leads.status='sold'` má konečně zapisovatele** (rozhodnutí: zapisovat): nový trigger `sync_lead_status_from_match` — vstup matche do `sold/invoiced/paid` přepne lead na `sold` (guard `status='matched'`), revert zpět na `watching/contracted/sale_reported` vrátí lead na `matched`. Pokrývá všechny zapisovatele match statusu (admin UI detail, inline select, budoucí routes) jedním místem; cancel větev dál řeší M-6. Dřív lead zůstával `matched` navždy a admin přehledy neuměly odlišit prodané.
  • • **Visící `sale_reported` se připomíná** (rozhodnutí: opakovaný CRON nag): nová fáze E v checkin-send — report starší 7 dní bez ověření → in-app připomínka aktivním adminům s počtem dní, opakuje se à 7 dní (nový sloupec `matches.sale_report_reminder_sent_at`, at-most-once claim vzor fází A–D). Dřív byl admin notifikován jen jednou při reportu a watchdog stav přeskakuje.
  • • **Duplicitní poptávka blokována i při běžící zakázce** (rozhodnutí: blokovat): kontrola duplicit v new-lead rozšířena o stav `matched` (parcela + jednotka + typ) — druhá poptávka na tutéž nemovitost během zakázky obcházela provizi.
  • • **Seller může stáhnout neschválenou poptávku** (rozhodnutí: povolit): H-D trigger **v4** whitelistuje `pending_review→cancelled`; tlačítko Zrušit + dialog v detailu i pro pending_review; `handleCancelLead` přijímá oba stavy. Přibalený FL-L fix: tlačítko Prodloužit se renderuje jen u `active`/`expired` (dřív disabled i u pending_review/rejected) + odstraněna mrtvá toast větev.
  • • **Follow-up — seller UI + RLS pro nový stav `sold`** (vlastní kontrola dopadu): seller detail/list byly klíčované na `status === "matched"` → po přepnutí na sold by zmizel kontakt makléře, reveal bidů i lead ze záložky Spárováno. Opraveno na `["matched","sold"]` (use-lead-detail, section-bids, section-lead-detail, seznam); **migrace `20260705032530`**: bids SELECT policy reveal větev `status IN ('matched','sold')` (ověřeno na živé DB: active 1/5 → 0 bidů, sold → bid viditelný).
  • • 282 testů, tsc + eslint + build ✓.
  • FL-L dávka 2: spolehlivost notifikací, audit přesnost, race conditions
  • • **Prázdné jméno → tichá ztráta e-mailu (v45):** EF vyžadovala `to_name` (400) — SQL triggery skládají `TRIM(first||' '||last)`, profil bez jmen = e-mail se tiše ztratil (`net.http_post` fire-and-forget). → `to_name` volitelné, prázdné/chybějící jméno = neutrální oslovení „uživateli" (šablony renderují „Dobrý den, X!" — prázdný string by dal „Dobrý den, !").
  • • **checkin-send v14 — at-most-once:** všechny 4 fáze (A/B/C/D) zapisovaly flag až PO odeslání → pád mezi sendem a flagem = duplicitní e-maily při dalším CRON běhu. → flag se zapisuje PŘED odesláním atomickým claimem (`.is(null)`, u fáze D optimistic lock na předchozí timestamp, + `.select()`) — kryje i souběžné běhy. Trade-off (pád po claimu = e-mail chybí) dokumentován: fázi A dožene B, C dožene D; duplicitní urgence jsou horší než chybějící.
  • • **expire-leads v9 — fáze A přestavěna:** `if (!emailData) continue;` přeskočil i in-app notifikaci a flag → seller bez e-mailu dostával warning recyklovaný každý den; flag po sendu = double-send okno. → atomický claim PŘED odesláním (flag se zapíše vždy — i bez e-mailu / vypnutých preferencí), e-mail lookup až po claimu, in-app se pošle i bez e-mailu. Inline šablony: HTML escaping user-controlled hodnot (jméno, lokalita — vzor M-2) + vokativ oslovení (lokální kopie `vocative.ts`).
  • • **Filtr aktivních adminů sjednocen:** `report-sale`, `confirm-sale-seller`, `confirm-contract`, `email-action` (2×) a checkin-send fáze B nefiltrovaly `status='active'` → suspendovaný admin dál dostával notifikace (watchdog-check/submit-rating/support-ticket filtr měly). → `.eq("status","active")` všude.
  • • **Audit přesnost (právní důkaz):** `report-sale` hardcodoval `changes old:"watching"` i pro match ve stavu `contracted` → nově loguje skutečný `match.status`. (Druhá půlka nálezu — `confirm-sale-seller` prázdné `changes` — už byla vyřešena přepisem FL-H4.)
  • • **handleCancelLead race:** UPDATE s `.eq("status","active")` při mezitím změněném stavu (matched/expired) vrátil 0 řádků bez chyby → zrušení vypadalo jako úspěch. → `.select()` + kontrola počtu řádků + hláška „stav se mezitím změnil".
  • • **Admin approve/reject optimistic lock:** zastaralá záložka mohla vrátit k přepracování už auto-schválený lead (CRON lock měl, admin UI ne). → `.eq("status", lead.status)` + `.select()` kontrola + toast; notifikace sellerovi jen při skutečné změně.
  • • Testy: mocky admin dotazů rozšířeny o `.eq(status)` řetěz. **282 testů, 25 suites**, tsc + eslint + build ✓.
  • AAL2 vynucení pro adminy — is_admin() + admin API routes
  • • **Migrace `20260705020900`**: `is_admin()` nově `role = 'admin' AND auth.jwt()->>'aal' = 'aal2'` (chybějící JWT/claim → false, fail-closed). Jeden chokepoint — pokrývá všech 27 policies + 7 SECURITY DEFINER funkcí/triggerů naráz; předem ověřeno, že VŠICHNI volající předávají `auth.uid()` (aktuální session), nikdy cílového uživatele. Nedotčeno: service_role/CRON (`auth.uid() IS NULL` bypassy PŘED voláním is_admin), seller/broker, admin UI (po MFA verify má aal2 — useAuthGuard fail-closed), čtení vlastního profilu před MFA (větev `id = auth.uid()`).
  • • **Admin API routes**: nový helper `lib/server/jwt-aal.ts` (`getTokenAal`/`isAal2` — dekóduje payload UŽ OVĚŘENÉHO tokenu, podpis neověřuje) nasazen v `send-notification`, `support-ticket/reply`, `test-watchdog` (token ze `getSession` — cookie-based klient) a `lead-photos` (admin větev autorizační matice jen s AAL2, jinak propad do 403).
  • • **Ověřeno na živé DB** (kandidát v DO bloku s rollbackem před aplikací + post-apply test): admin AAL1 → `is_admin = false`, `profiles` vrátí jen vlastní řádek (1 z 13); admin AAL2 → `true`, všech 13; seller s AAL2 → false; bez JWT → false. Security advisors bez nových nálezů.
  • • 7 nových testů (`jwt-aal` 5, AAL1-admin → 403 v `test-watchdog` a `lead-photos`) — **282 celkem, 25 suites**. tsc + build ✓.
  • Právní dokumenty v2.1 — revize proti reálnému chování aplikace
  • • **VOP**: nový čl. 4.4 moderace Poptávky (schválení/24h auto-approve/zrušení + odmítnutí bidů); platnost 30 dnů od zveřejnění + pravidla prodloužení (posledních 7 dní / po expiraci); odstraněny neexistující „2 rezervní Nabídky"; jeden **aktivní** Match + nový 7.7 (zrušený Match vrací Poptávku do oběhu — M-6); hodnocení: vyjádření makléře přes Provozovatele (self-service neexistuje); 12.4 + stav `contracted`; 13.3 povinné notifikace = match in-app + systémová (Check-in e-maily vypnutelné); vodoznak + EXIF strip fotek; SMS jen CZ/SK.
  • • **RS makléře**: 4.2 schvalování registrace + výmaz zamítnutých po 7 dnech; 4.4 bez nepravdivého záznamu IP adresy (jen timestamp + verze); 11.4/11.5 hodnocení sladěno; 13.1/13.2 + `contracted`/`sale_reported`.
  • • **Privacy**: SMS verifikace obou rolí; nový účel 3.18 bezpečnostní logy/rate limiting (IP, 24h); sekce 6 + EXIF strip a privátní úložiště fotek; 8.1 rozsah výmazu per Match; 8.5 retence snapshotu 1 rok/4 roky (M-9, dřív chybně „3 roky").
  • • **Registrační modaly**: odstraněno nepravdivé „Veškeré zpracování probíhá v EU" — doplněny USA transfery (Resend, Sentry, Google) se SCCs/DPF.
  • • LEGAL-*.md snapshoty synchronizovány s TSX. tsc + build ✓.
  • FL-M14–M19: notifikační preference + dual-write, audit, telefon, auth guardy, únik ze spinneru
  • • **FL-M14 — notifikační preference ignorovány / dual-write díry v match flow:** confirm-contract preference fetchoval a nepoužíval; report-sale sellera nenotifikoval VŮBEC (jen adminy); confirm-sale-seller posílal brokerovi jen in-app; email-action preference vůbec nefetchoval. → Nový sdílený helper `notifyCounterpart` (`lib/server/notify-counterpart.ts`): pref-gate (`cooperation` = smlouva, `sale_updates` = potvrzení prodeje; opt-out model) + dual-write in-app (`create_notification`) i e-mail (`sendNotificationEmail`, lookup adresy až PO pref checku). Nasazen ve všech 4 místech — webové routy i email-action větve teď produkují identické notifikace. Nová e-mail šablona `sale_confirmed_counterpart` (směr per `confirmed_by_role`, CTA z klíče příjemce — FL-H2 pravidlo, cena pokud nahlášena). Seller UI dostal řádek „Potvrzení prodeje" (`sale_updates`) — nový typ jde vypnout. Admin e-maily v email-action sjednoceny z `functions.invoke` na `sendNotificationEmail` helper.
  • • **FL-M15 — zámek „match in-app nelze vypnout" byl jen v UI:** přímý PATCH `notification_preferences.match.inapp=false` (sloupec není v C2 zámku) notifikaci vypnul — `notify_match_created` preferenci četl, zatímco `handle_match_cancelled` posílal vždy (dva kontrakty na stejném typu). **Rozhodnutí: vynutit sender-side** — migrace odstranila oba `get_notification_pref(...,'match','inapp')` gaty z `notify_match_created` (in-app match = operační notifikace, posílá se VŽDY); e-mail gate zůstává, UI lock zůstává a nově říká pravdu. FL-H2 `match_id` payload nedotčen (ověřeno na živé DB: notifikace dorazily oběma stranám navzdory `inapp=false`).
  • • **FL-M16 — submit-rating audit log se od M-4 tiše nezapisoval:** inline INSERT do `audit_logs` šel přes user-JWT klienta, ale M-4 zrušil authenticated INSERT policy → RLS zápis tiše zamítala (jen console.error). → `logAuditAction(supabaseAdmin, ...)` (vzor report-sale); `submit_rating` v ACTION_LABELS už byl.
  • • **FL-M17 — telefon v profilu bez validace = trvalý zámek na verify:** profil ukládal RAW číslo (SMS EF ho pak odmítla → nekonečná verify smyčka bez úniku) a vymazání čísla nechalo `phone_verified_at` („ověřený" účet bez telefonu). → Nový helper `diffPhoneUpdate` (`lib/utils/phone.ts`): normalizace + validace jako při registraci, `changed` po normalizaci (přeformátování ≠ změna), vymazání nuluje i verified flag; inline hint v UI. Verify stránka nově umí ZMĚNIT existující neověřené číslo („Špatné číslo? Změnit číslo" → input mód, po uložení nový OTP flow) — úniková cesta z neověřitelného čísla.
  • • **FL-M18 — tři stránky obcházely `useAuthGuard`:** `broker/bids`, `seller/leads` (ad-hoc `getUser()` + role check) a `broker/matches/[id]` (ani role check) — broker se zastaralými podmínkami mohl potvrdit smlouvu/nahlásit prodej, bez kontroly suspenze/verifikace/pending_approval. → Všechny tři na `useAuthGuard({ allowedRoles })`, fetch gated na `profile`, singleton `supabase` z guardu.
  • • **FL-M19 — trvalé selhání načtení profilu = nekonečný spinner:** `fetchProfile` retryoval jen PGRST116 a jiné chyby nechaly `profile=null` bez signálu; `useAuthGuard` naplánoval jediný refetch a pak `isLoading=true` navždy; login stránka totéž. → Nové aditivní pole `profileError` v AuthContextu (23 konzumentů beze změny), guard po neúspěšném refetchi přesměruje na novou stránku `/auth/profile-error` („Zkusit znovu" = reload s auto-redirectem po úspěchu, „Odhlásit se" = poslední únik); login stránka spinner při chybě pouští a přesměruje tamtéž.
  • • 29 nových testů (`notify-counterpart` 8, `submit-rating` 5, `report-sale` 4, `email-action` +5, `diffPhoneUpdate` 7) — **275 celkem, 24 suites**; `server-only` mock v setup.ts. tsc + eslint + build ✓.
4. července 2026
  • FL-M6–M13: leads workflow — extend/cancel/edit hardening + admin UI + reálné počty bidů
  • • **FL-M8 — zrušení leadu nechávalo bidy `pending` navždy a dialog lhal:** nový DB trigger `handle_lead_cancelled` (AFTER UPDATE `→cancelled`): pending/seen bidy → `rejected` + in-app notifikace dotčeným makléřům (typ `lead_cancelled`; rejected bidy nechává — M-6 evidence; accepted na cancelled leadu neexistuje). Spustí se i při GDPR cancelu. Dialog sellera nově počítá z `bidCount` RPC (dřív `bids.length` = před revealem 0) — slibované chování už je pravda.
  • • **FL-M9 — seller dashboard + seznam ukazovaly 0/5 do revealu:** přímý SELECT z `bids` pod H-E RLS gate vracel 0 řádků → mrtvý „collecting" stav, skok 0/5→5/5. Nová bulk RPC `lead_bid_counts(uuid[])` (SECURITY DEFINER, non-rejected — v syncu s `lead_bid_count`; guard vlastnictví, REVOKE anon/public) nasazená v `dashboard/page.tsx` + `seller/leads/page.tsx`.
  • • **FL-M10 — expirovaný lead s 1–4 bidy = slepá ulička s klamavým UI:** (a) lock screen u `expired` říká pravdu (nabídky se nezobrazí, CTA prodloužit/nová poptávka) místo „zobrazí se při 5" (na ne-aktivní lead bidovat nejde); text „makléři vaši poptávku vidí" jen u `active`. (b) extend počítá `max(now, stará expirace) + 14 dní` — extend leadu vypršelého před >14 dny už nevytvoří active lead s expirací v minulosti (CRON by ho hned vrátil a propálil jedinou šanci).
  • • **FL-M11 — extend: mrtvá API route + pravidla nevynucená v DB:** reálný extend byl client-side UPDATE (dokumentovaný rate limit 5/min se neuplatnil, pravidlo „posledních 7 dní" jen v UI, seller mohl přímým PostgREST posouvat expiraci a resetovat flag). → H-D trigger nově pinuje `expires_at` + `extension_used` + `expiry_warning_sent_at` pro ne-admina a whitelist větev `expired→active` odstraněna; `/api/extend-lead` oživena jako JEDINÁ cesta (Bearer auth, validace včetně 7denního okna, zápis pod service_role, reset `expiry_warning_sent_at` — prodloužený lead zase dostane varování, audit). `handleExtendLead` volá route.
  • • **FL-M13 — editace obsahu schváleného leadu nebyla blokována server-side:** H-D trigger pinuje obsahové sloupce (adresa, GPS, fotky, params, katastr, typ/kategorie, exclusivity) pro ne-admina, pokud `status NOT IN ('pending_review','rejected')` — odpovídá UI, edit/resubmit flow nedotčen.
  • • **FL-M6 — admin leads UI s neexistujícím stavem `closed`:** volba = CHECK violation. Filtr seznamu přestavěn na reálný výčet (přidány `sold`/`cancelled`, `closed` pryč); badge sjednoceny do `lead-status-badge.tsx` (mrtvé case `closed`/`pending`/`accepted` odstraněny, `sold`/`cancelled` doplněny).
  • • **FL-M7 — volný admin status-select bez pravidel:** `pending_review→active` selectem vytvářel nikdy neexpirující lead bez review metadat a notifikace. Nová mapa `VALID_LEAD_TRANSITIONS` (`lead-transitions.ts`): schválení/zamítnutí VÝHRADNĚ tlačítky, selectem jen `active→cancelled`; bez povolených přechodů se místo selectu zobrazí badge. `handleStatusChange` validuje + nově zapisuje audit log (`change_lead_status`).
  • • 8 nových testů extend-lead route (246 celkem), tsc + eslint + build ✓.
  • FL-M1–M5: email-action guardy + konzistence admin match workflow
  • • **FL-M1 — email-action POST bez guardu na stav matche:** 14denní token zapsal potvrzení smlouvy/prodeje i do `cancelled`/`sold`/`invoiced`/`paid` matche a rozeslal notifikace protistraně/adminům. → Guard whitelist stavů per akce zrcadlí webové routy (`contract_confirm` → watching/contracted/sale_reported; `sale_confirm` broker → watching/contracted jako report-sale; seller → +sale_reported jako confirm-sale-seller). Mimo whitelist → `{ irrelevant: true }`, stránka `/email-action/success` zobrazí „Akce už není potřeba" (token zůstává záměrně spotřebovaný — koncový stav).
  • • **FL-M2 — token se propálil při selhání akce:** selhání fetche matche vracelo 404 s už zkonzumovaným tokenem; `UPDATE matches` se nekontroloval → při chybě uživatel viděl úspěch, notifikace odešly, audit tvrdil `status_changed`, ale match se nezměnil a retry vracel „already". → Oba zápisy kontrolovány; při selhání se token vrátí (`responded_at = NULL`, helper `refundToken`) + 500 „zkuste znovu". Atomická konzumace před akcí (double-click ochrana) zůstává.
  • • **Přibalený fix:** `email-action/page.tsx` POST handler četl `data.already` z nerozbaleného JSONu (`apiSuccess` balí do `{ data }`) → `already` větev z POSTu nikdy nefungovala (kryla ji jen GET pre-validace). Opraveno spolu s napojením `irrelevant`.
  • • **FL-M3 — zamítnutí nahlášeného prodeje trvale vypnulo fáze C/D:** `handleRejectSaleReport`, `handleRevertToWatching` i inline select `sale_reported→watching` vracely status, ale nechávaly `sale_reported_at` (fáze C/D filtrují `IS NULL`) a `seller_confirmed_sale_at` (per-party skip sellera) → match už nikdy nedostal automatický sale check. → Všechna 3 místa nulují `sale_reported_at/price/date` + `seller_confirmed_sale_at/price` (admin rozhodl, že prodej nebyl — potvrzení sellera je tím také neplatné).
  • • **FL-M4 — admin-set `contracted` nikdy nespustil fázi C:** inline select zapisoval jen `{status}`, ale fáze C kotví na `contract_signed_by_broker_at` → NULL = match navždy vyloučen ze sale checku. → Při přechodu na `contracted` se doplní `contract_signed_by_broker_at = now()` (jen pokud NULL — opakovaný přechod timestamp nepřepíše).
  • • **FL-M5 — plomba box neakceschopný ve stavu, kdy plomba chodí:** watchdog detekuje plombu ve `watching`/`contracted`, ale akční tlačítka na detailu byla jen pro `sale_reported`/`sold` — text „potvrďte prodej níže" ukazoval na nic. → Nové tlačítko „Označit jako prodáno (plomba)" (`handleMarkSoldFromPlomba`: `sold` + `sale_detected_at` + cena z inputu, audit akce `mark_sold_plomba`); navazuje existující „Potvrdit prodej".
  • • 8 nových testů POST /api/email-action (guardy, refund, race, expirace — 238 celkem), tsc + eslint + build ✓.
3. července 2026
  • FL-H3 + FL-H4: Karta spolupráce mizela ve stavu `contracted` + seller sám měnil stav matche
  • • **Broker UI** (`section-cooperation.tsx` + hook): karta pro `watching`/`contracted`/`sale_reported`; formulář „Nahlásit prodej" ve `watching` i `contracted`; nový info box v `sale_reported` („Prodej byl nahlášen a čeká na ověření administrátorem").
  • • **Seller UI** (`section-collaboration.tsx`): karta + blok „Prodej" pro `watching`/`contracted`/`sale_reported` (potvrzení sellera status nemění, jde o korroboraci).
  • • **`confirm-sale-seller`**: zapisuje JEN `seller_confirmed_sale_at` (+ volitelná cena) — status mění výhradně brokerův report nebo admin; audit loguje skutečný zápis. Admin viditelnost beze změny (box „Potvrzeno prodávajícím" + notifikace adminům existovaly už dřív). Komentář v email-action aktualizován (větve jsou nyní sjednocené).
  • • **checkin-send v13** (doplněk, vyřešen i LOW nález z auditu): fáze C/D sale check per-party skip — seller, který prodej už potvrdil, se znovu neobesílá každé 3 měsíce; broker ano, dokud nenahlásí. Nasazeno přes CLI (verify_jwt=false), smoke test 200 OK (`{success:true, ...}`).
  • • tsc + eslint + build + 230 testů ✓.
  • FL-H2: Deep-linky brokera vedly na seznam místo detailu zakázky + vadné e-mail CTA
  • • **Stránka překlíčována na `match_id`** (`use-broker-match-detail.ts`): dual lookup — primárně `.eq("id", param)`, fallback `.eq("lead_id", param)` pro legacy odkaz z `/broker/bids` (UNIQUE(lead_id, broker_id) garantuje ≤1 match). Tím se ~7 send sites opravilo bez zásahu (většina už match.id posílala).
  • • **`match_broker` CTA**: trigger `notify_match_created` nově posílá `match_id` v datech e-mailu (migrace); šablona `match.ts` linkuje `/broker/matches/{match_id}` s fallbackem `/broker/bids?filter=accepted`. Nová stránka `/broker/matches` (bez ID) = redirect na seznam akceptovaných nabídek (kryje případné staré e-maily).
  • • **`contract_signed_notification`**: `confirm-contract` a `email-action` posílají jen klíč příjemce (potvrdil seller → broker dostane `match_id`; potvrdil broker → seller `lead_id`) — šablona beze změny, ternár teď vybírá správně.
  • • **Přibalený fix (nález z auditu, chyběl v TODO):** seller detail — match dotaz `.eq("lead_id", ...)` doplněn o `.neq("status","cancelled")` + `.maybeSingle()`; po M-6 může mít lead zrušený i nový match a `.single()` by spadl na 2 řádcích (seller by ztratil match UI).
  • • **Ověřeno:** živá DB (DO blok + rollback) — in-app notifikace brokera má href `/broker/matches/<match_id>`, e-mail payload `match_broker` obsahuje `match_id`, lead po create_match `matched` (FL-K1 sanity). EF v43 nasazena přes CLI (`verify_jwt=false` zachováno, ověřeno přes list_edge_functions). tsc + eslint + build + 230 testů ✓.
  • FL-H1: Rozjeté počítání bidů rozbíjelo 2. kolo po zrušení matche + anon četl view leadů
  • • **View**: obě `count(*)` subquery (sloupec `bid_count` + WHERE) → `AND status != 'rejected'`; CREATE OR REPLACE (zachová granty), `security_invoker=false` beze změny (C3 column-masking boundary). Komentář: musí zůstat v syncu s `lead_bid_count`/`check_bid_limits`. Brokerům se tím opraví i „X/5 obsazeno" na kartě leadu po reopenu.
  • • **`notify_new_bid`**: `COUNT(*)` → `lead_bid_count(NEW.lead_id)` (single source of truth) — notifikace „Nabídky jsou připraveny!" přijde přesně při 5. reálné nabídce, jednou.
  • • **Vedlejší nález (opraven tamtéž):** `anon` měl na view VŠECHNA práva — C3 migrace dělala DROP+CREATE a Supabase **default privileges** při CREATE znovu přidaly granty pro anon/authenticated/service_role (původní `GRANT SELECT TO authenticated` záměr tak byl tiše přepsán). Kdokoli s public anon klíčem (je ve frontend bundle) mohl bez přihlášení číst anonymizované aktivní leady. → `REVOKE ALL FROM anon` + REVOKE ne-SELECT práv authenticated. **Gotcha do budoucna: po DROP+CREATE view/tabulky vždy zkontrolovat granty.**
  • • **Ověřeno na živé DB (DO blok + rollback, simulace 2. kola se 7 brokery):** evidence bid → `bid_count=0`, lead viditelný; po 4 nových bidech viditelný, žádná notifikace; 5. bid → view skryje, `lead_bid_count=5` (reveal gate), přesně 1 notifikace; 6. bid odmítnut `check_bid_limits`; granty anon=false/authenticated SELECT only; `security_invoker=false` zachováno. Frontend beze změn (jen dostává správné hodnoty).
  • Audit logičnosti flow + KRITICKÝ fix FL-K1: výběr makléře nefungoval (create_match vs. H-D trigger)
  • • **Fix (stavový důkaz, bez GUC):** trigger povolí `active→matched` ne-adminovi JEN pokud pro lead existuje ne-zrušený match — `EXISTS (... matches WHERE lead_id = OLD.id AND status != 'cancelled')` (kryje partial index `matches_lead_id_one_active`). `create_match` vkládá match PŘED `UPDATE leads` (pořadí je nosné, komentováno). Seller stav nezfalšuje: `matches` nemá žádnou INSERT policy (klientský INSERT RLS zamítne — **NEPŘIDÁVAT**, výjimka na tom stojí).
  • • **FL-M12 — guardy v `create_match`:** `FOR UPDATE OF l` zámek řádku leads (serializace souběžných akceptací — druhá skončí čistou chybou, ne unique violation; stejné pořadí zámků jako `check_bid_limits` → bez deadlocku), guard na neexistující bid (dřív NULL-unsafe `!=` check proklouzl), NULL-safe ownership (backend/admin/vlastník), guard `lead.status='active'` (nelze matchnout expirovaný lead přímým RPC), whitelist stavů bidu `pending`/`seen` (evidence `rejected` bid po M-6 zrušení nelze re-matchnout; `accepted` → „již byla přijata"). `SET search_path TO ''` + plná kvalifikace (konvence).
  • • **P1 (nalezeno při plánování):** `anon` měl na `create_match` EXECUTE (default PUBLIC grant) — anonymní PostgREST request má `auth.uid()` NULL, takže backend větev by byla zneužitelná → `REVOKE EXECUTE FROM anon, public; GRANT TO authenticated, service_role`.
  • • **Frontend:** `handleAcceptBid` (`use-lead-detail.ts`) místo zahazování chyby mapuje hlášky DB guardů na srozumitelné chyby (vzor `check_bid_limits` mapování v broker detailu).
  • • **Opravou se obnovila 3 rozbitá navazující chování:** reveal bidů sellerovi po matchi (RLS větev `matched`), uzavření leadu pro další bidy (`check_bid_limits` vidí `matched`) a funkční M-6 reopen po zrušení matche.
  • • **Ověřeno na živé DB (DO blok + rollback, 8 testů):** happy path pod seller JWT (lead `matched`, bid accepted, ostatní rejected, match vznikl), druhá akceptace → „Poptávka již není aktivní", M-6 cancel → lead zpět `active` + evidence bid + re-match blokován, přímý seller UPDATE `active→matched` bez matche dál tiše blokován, expirovaný lead / cizí seller / neexistující bid → správné chyby, granty anon=false/authenticated=true. Snapshot `supabase/reference/schema.sql` aktualizován. tsc + eslint + build + testy ✓.
  • Fix: 1-click odkazy z e-mailů nefungovaly pro nepřihlášené uživatele
  • • **Nový `GET /api/email-action?token=...`**: server-side validace tokenu pod service_role (vzor existujícího POST) — vrací jen minimum pro zobrazení (`action_type`, `location`, `already`, `expired`). Stejný rate limit bucket jako POST (10/min).
  • • **`email-action/page.tsx`**: přímý Supabase dotaz nahrazen fetchem na GET endpoint; RLS zůstává zamčené beze změny (žádná nová policy).
  • • E2E ověřeno na produkci: fáze A (contract prompt 7d), B (reminder 21d), C (sale check 42d), D (opakovaný 3m) + 1-click potvrzení smlouvy/prodeje oběma rolemi, idempotence opakovaného kliknutí, pravidlo důvěryhodnosti (seller sám stav nemění), neplatný token 404.
  • • 6 nových testů (230 celkem), tsc + eslint + build ✓.
  • Pre-launch audit — M-1: Rate limit obejitelný přes podvrženou IP + in-memory stav
  • • **(A) `getClientIp` trusted-hop model** (`frontend/src/lib/rate-limit.ts`): reálná IP klienta se bere z `x-forwarded-for` na pozici `len - TRUSTED_PROXY_HOPS` (env, default 1 = Traefik, který reálnou IP klienta připojuje jako poslední záznam) — vše nalevo je klientem podvržené a ignoruje se. Vytažená hodnota se validuje přes nový `isValidIp` (IPv4/IPv6). `x-real-ip` je jen fallback (za proxy), NE primární zdroj — Traefik ji nemusí nastavovat a klientem zaslaná by byla spoofovatelná. Nevalidní/chybějící důvěryhodný hop → fail-closed na sdílený `unknown` bucket (přísnější, ne obejitelný). Připraveno na budoucí Cloudflare (`TRUSTED_PROXY_HOPS=2` nebo `CF-Connecting-IP`).
  • • **(B) DB-backed rate limit pro 5 kritických endpointů**: nový helper `lib/rate-limit-db.ts` (`checkRateLimitDb`) počítá requesty v okně přes tabulku `api_rate_limit` — na rozdíl od in-memory přežije deploy a je sdílený napříč instancemi. Nasazeno na `verify-email` (brute-force tokenu), `check-availability` (enumerace), `support-ticket` (spam), `ico-lookup` + `cadastral-lookup` (pálení kvót ARES/ČÚZK). **fail-open + Sentry** při chybě DB (dostupnost webu > striktnost limitu). Typeahead/UX endpointy (`address-suggest`, `reverse-geocode`, `lead-photos`, `audit-log`) zůstávají in-memory (DB zápis per stisk klávesy je nežádoucí).
  • • **Tabulka `api_rate_limit`** (`bucket_key`, `endpoint`, `hit_at`): RLS enabled **bez policies** → přístup jen service_role; index `(endpoint, bucket_key, hit_at)`; cleanup CRON `cleanup-api-rate-limit` denně 5:00 UTC (záznamy starší 24h). Ověřeno na živé DB (RLS on, index, 0 policies, cron `0 5 * * *`).
  • • Známý přijatý tradeoff: count-then-insert není atomické (TOCTOU) — pro rate limiting akceptovatelné (krátký burst přes limit). Nový env `TRUSTED_PROXY_HOPS=1` v `.env.example`. 14 nových testů (224 celkem), tsc + eslint + build ✓.
  • • **Infra ověřeno v produkci (3. 7.):** `TRUSTED_PROXY_HOPS=1` nastaveno v Coolify; dočasnou route `debug-clientip` změřeno, že `x-forwarded-for` nese jediný hop (Traefik) = reálná IP klienta; spoof test z externího stroje potvrdil, že Traefik **přepisuje** `x-forwarded-for` i `x-real-ip` reálnou IP → podvržené hlavičky se do appky nedostanou. Měřicí route poté smazána. Zbývá jen ověřit, že Next.js kontejner není dosažitelný napřímo mimo Traefik (`docker ps` / `nmap`).
2. července 2026
  • Pre-launch audit — M-6: Zrušený match natrvalo zablokoval lead
  • • **Částečný unikátní index** `matches_lead_id_one_active` (`WHERE status != 'cancelled'`) místo plného `UNIQUE(lead_id)` — zrušené matche zůstávají v historii (důkazy), nový match je po zrušení povolen; dva aktivní matche na jeden lead dál nemožné.
  • • **Nový trigger `on_match_cancelled_reopen_lead`** (AFTER UPDATE na `matches`): při přechodu `watching/contracted/sale_reported → cancelled` vrátí lead do oběhu — status `active`, čerstvá expirace +30 dní, reset `extension_used`/`expiry_warning_sent_at`. Ze `sold/invoiced/paid` se lead NEvrací (nemovitost prodána, řeší admin ručně).
  • • **Reset slotů (2. kolo)**: nabídka zrušeného makléře → `rejected` (zůstává jako důkaz — FK z matches, ON DELETE CASCADE by smazal match!); ostatní zamítnuté nabídky bez vazby na match se **mažou** → sloty 0/5, původní makléři mohou nabídnout znovu (zrušený makléř ne — `UNIQUE(lead_id, broker_id)`). Rozhodnutí 2026-07-02.
  • • **`lead_bid_count()` počítá jen non-rejected** — sjednocení s `check_bid_limits`; bez toho by reveal policy po resetu odhalila nové nabídky okamžitě (staré rejected bidy splnily `>= max_bids`) → rozbité FOMO 2. kola. Běžný flow beze změny (před matchem rejected bidy neexistují).
  • • **GDPR guard**: seller se smazaným účtem (`deleted_at`) → lead se místo `active` zruší (anonymizovaný lead se nikdy znovu nepublikuje) a notifikace se neposílá.
  • • **Notifikace**: in-app sellerovi („poptávka je znovu aktivní", deep-link na detail). Makléřům se záměrně nic neposílá — lead se tiše objeví v seznamu (rozhodnutí 2026-07-02). Admin UI: dialog zrušení + toast informují o vrácení leadu do oběhu.
  • • Ověřeno na živé DB (DO blok s vynuceným rollbackem): labely, blokace 2. aktivního matche, revert leadu (+30d, sloty 0/5, 4 bidy smazány, bid matche rejected, notifikace), rebid původního makléře + nový match po zrušení, GDPR guard větev. tsc + eslint + build + 210 testů ✓.
  • Pre-launch audit — M-11: Cenový semafor se zapisoval už od 1./3. nabídky
  • • Starý trigger + jeho argless funkce `calculate_price_label()` odstraněny; smazány i 2 osiřelé trigger funkce (`trigger_calculate_price_label`, `trigger_recalculate_lead_price_labels`), na které žádný trigger neukazoval.
  • • Zůstává jediný mechanismus: `tr_calculate_price_labels_at_max_bids` — při dosažení `max_bids` hromadný přepočet všech nabídek (medián ze všech non-rejected, ±15 %). Rozšířen o `UPDATE OF price_min, price_max` (admin editace ceny po revealu nesmí nechat zastaralé labely).
  • • Datový fix: vynulovány předčasné labely na leadech pod `max_bids`; leady na plných slotech přepočítány novou logikou; matched leady beze změny (historicky zobrazené labely).
  • • Frontend beze změny — před revealem jsou labely `NULL`, badge se nezobrazí.
21. června 2026
  • Fix — Google OAuth (login i registrace) přesměroval na localhost
  • • **Důkaz:** návštěva `https://spravnymakler.cz/auth/callback` (bez `code`) → 307 na `https://localhost:3000/auth/login?error=missing_code`.
  • • **Nový helper `lib/site-url.ts`** — `SITE_URL` = `NEXT_PUBLIC_SITE_URL` (override pro dev) s produkčním fallbackem `https://spravnymakler.cz`. Záměrně **NE** z `x-forwarded-host` (lze podvrhnout → open-redirect/phishing).
  • • **Opraveny 3 route handlery** (stejný vzor): `auth/callback/route.ts` (OAuth — 7 redirectů, `origin = SITE_URL`), `auth/verify-email/route.ts` (ověření e-mailu — 4 redirecty, base `SITE_URL`), `api/checkin-respond/route.ts` (legacy F10 — 6 redirectů). Čtení query params z requestu zůstává z `request.url`.
  • • **Env:** `NEXT_PUBLIC_SITE_URL=http://localhost:3000` přidáno do `.env.local` (dev — aby lokální OAuth dál mířil na localhost) i `.env.example`. Produkce běží přes hardcoded fallback (Coolify env nutný není).
  • • **Deploy:** vyžaduje redeploy frontendu na Coolify. tsc/eslint + `next build` ✓.
12. června 2026
  • Pre-launch audit — GDPR trojice M-7 + M-8 + M-9 (delete-account)
  • • `support_tickets` — DELETE (řádky uživatele + anonymní tickety se stejným e-mailem z kontaktního formuláře); jméno/e-mail/text bez důkazní hodnoty (rozhodnutí: smazat úplně).
  • • `email_action_tokens` — DELETE (živé 1-click contract/sale tokeny by jinak fungovaly i po smazání účtu).
  • • `broker_ratings.comment` — u sellera vyčištěn na NULL (volný text může identifikovat); číselná hodnocení zůstávají (agregát pro profil makléře).
  • • `auth.identities` — nová SECURITY DEFINER funkce `scrub_auth_identities` redukuje `identity_data` na `{sub, deleted_e-mail}` (e-mail identita držela jméno/telefon/IČO/firmu z registrace, Google navíc avatar/full_name). `sub` zůstává (opaque ID pro GoTrue konzistenci, uživatel je zabanovaný) — akceptované reziduum.
  • • `audit_logs` se ZÁMĚRNĚ nedotýkáme (právní důvod, self-contained metadata).
  • • Ověřeno na živé DB (transakce + rollback): `scrub_auth_identities` zredukoval identity (PII pryč, `sub` zachován); snapshot insert s `purge_after` +1 rok pro 0 matchů; nové CRON joby zaregistrovány. OTP šablona ověřena doručeným e-mailem. tsc + eslint + build + 210 testů ✓. (OAuth-only detekci nelze živě vyvolat — Google účet nejde přes API; ověřeno code review.)
  • Pre-launch audit — M-3: Watchdog nastavoval `sold` při JAKÉkoli plombě
  • • **`watchdog-check` v19**: detekce **NEmění status** — zapisuje `plomba_detected_at` + `plomba_typ_rizeni` + `plomba_rizeni_id` (sloupce existovaly, kód je neplnil; admin UI je už zobrazovalo). Admin rozhodne přes existující `confirm_sale`. **Dedup přes MNOŽINU řízení** (`plomba_rizeni_id` = CSV všech běžících řízení): na LV může běžet víc plomb současně (např. hypotéka + následný prodej) — každé dosud neviděné řízení vyvolá novou notifikaci, stejná množina se nehlásí každé pondělí; dokončená řízení se z uloženého seznamu tiše odmazávají. `plomba_typ_rizeni` = unikátní typy všech řízení. Bonus: plní `last_watchdog_check` u každé kontroly (observabilita, admin list ho zobrazuje).
  • • **Šablona `plomba_detected`** (send-notification-email v41): odstraněno „Status byl automaticky změněn na Prodáno" → „status NEBYL změněn, prověřte typ řízení na LV"; kroky přepsány (prodej → potvrdit v admin panelu, jinak nechat — watchdog sleduje dál); tlačítko deep-link na `/admin/matches/{id}`.
  • • **Admin UI**: plomba box v detailu matche doplněn o výzvu „Prověřte na LV — pokud jde o převod vlastnictví, potvrďte prodej."
  • • Deploy obou EF (CLI). Ověřeno živým během přes `net.http_post`+Vault: checked 2, statusy zůstaly `watching`, `last_watchdog_check` zapsán. Detekční větev ověřena code-review (reálnou plombu nelze vyvolat na povel). tsc + build ✓.
  • Pre-launch audit — M-2: HTML injection v e-mailových šablonách
  • • **Escaping na hranici** (jeden chokepoint, šablony beze změny): nový `send-notification-email/html.ts` — `escapeHtml()` (`& < > " '` → entity) + `escapeDataStrings(data)` (jen stringy; čísla zůstávají kvůli `.toLocaleString()`). `index.ts` v24: `template.html(escapeDataStrings(payload.data), escapeHtml(greetingName))`. Subjecty jsou statické — netýká se jich.
  • • URL kontexty bezpečné: HTML entity v atributech jsou validní (`&amp;` v href se dekóduje), UUID tokeny beze změny, ISO datumy pro `new Date()` escaping nemění.
  • • **Deploy v40** (CLI). **Ověřeno end-to-end doručeným e-mailem**: testovací payload s `<h2>`, falešným `<a href>` a pokusem o útěk z atributu (`"><img onerror=...>`) → v doručeném HTML vše escapované, tagy se zobrazují doslova jako text, žádný klikací odkaz. Žádné frontend změny.
  • Pre-launch audit — DB dávka M-4 + M-5 + M-12 (audit log, bids, anti-bypass)
  • • Policy zrušena → INSERT výhradně **service_role**. Nová proxy **`POST /api/audit-log`** pro client-side zápisy: identita + role odvozeny server-side z JWT + `profiles` (klient je neposílá), `action` proti whitelistu `ACTION_LABELS`, payload limity (100 znaků / 8 KB), rate limit 30/min.
  • • `lib/audit.ts` (server) nově vyžaduje service_role klienta — 5 API routes přepnuto na `supabaseAdmin` (extend-lead, report-sale, confirm-contract, confirm-sale-seller, checkin-respond-web). Nový **`lib/audit-client.ts`** (`logAuditClientAction`) — ~25 client callsites ve 12 souborech přepnuto, nepotřebné `auth.getUser()` fetche odstraněny.
  • • **Akceptováno**: uživatel může zalogovat whitelistovanou akci s vymyšlenými metadaty pod vlastní identitou — kritické monetizační akce se logují server-side v routes.
  • • Migrace `20260612131655_m4_m5_m12_audit_bids_filter_hardening.sql`. Ověřeno na živé DB (transakce + rollback): authenticated INSERT zamítnut; broker self-accept = 0 řádků + create_match OK; telefon v `services_other` → `[tel. odstraněn]` + flag. 9 nových testů (210 celkem), tsc + eslint + build ✓.
  • Pre-launch audit — G-8: Deanonymizace leadů přes fotografie + M-10 EXIF fallback
  • • **Bucket → private** (migrace `20260612093432_g8_private_property_photos_bucket.sql`). Public URL v DB zůstávají jen jako kanonické identifikátory; `delete-account` EF funguje beze změny (extrahuje cestu splitem). Bonus: INSERT policy zpřísněna na vlastní složku (`(storage.foldername(name))[1] = auth.uid()`, dřív šlo nahrát kamkoli v bucketu).
  • • **Nová API route `GET /api/lead-photos?leadId=`** — Bearer JWT + autorizační matice: admin/seller(vlastník)/broker-s-matchem → interiér+exteriér; broker bez matche → JEN interiér a JEN u leadu viditelného přes `leads_for_brokers` (active + volné sloty); jinak 403. Podepisuje batch `createSignedUrls` pod service_role, expirace 1 h. Rate limit 20/min.
  • • **Hook `useSignedPhotos(leadId)`** — mapa kanonická → signed URL; nasazen v 6 zobrazovacích místech (seller detail, seller edit, broker lead detail, broker match detail, admin detail ×2 galerie). `PhotoUpload` dostal `displayUrls` prop (edit) + module-level blob preview cache pro čerstvé uploady (přežívá unmount wizard kroků; `getLocalPhotoPreview` pro step-5 souhrn).
  • • **Watermark při uploadu** (`lib/utils/watermark.ts`) — centrované logo SM (`frontend/public/watermark-logo.svg`), 50 % šířky, opacity 0.2, rotace −20°, bílý tint + stín (viditelné na světlých i tmavých fotkách). Aplikuje se po kompresi, před uploadem; při selhání se soubor zamítne. Stávající (testovací) fotky bez watermarku — akceptováno.
  • • **Varování pro sellery** v upload UI: nepoužívat fotky ze starších inzerátů (jediný vektor, kde reverse search najde adresu).
  • • **M-10 — EXIF fallback**: při selhání canvas komprese (= EXIF/GPS strip) se dřív nahrál **originál s GPS polohou nemovitosti**. Nově se soubor zamítne s chybovou hláškou — neočištěný soubor nikdy neopustí prohlížeč.
  • • **Akceptováno vědomě**: `userId` v cestě signed URL (jen korelace, profil přes RLS nepřečtitelný); reverse search přihlášeným brokerem na dřív zveřejněných fotkách (inherentní — mitigace varování + watermark + identifikovaný broker se smluvním postihem).
  • • Verifikace: 12 nových vitest testů autorizační matice (201 celkem) ✓, tsc ✓, build ✓; živě ověřeno — public URL fotky vrací 400.
11. června 2026
  • Pre-launch audit HIGH — Skupina 6: Dependency audit + Next.js (H-G)
  • • `npm audit fix` (non-force) — vyčistil **high** transitivní (`fast-uri`, `minimatch`) + moderate (`ajv`, `brace-expansion`).
  • • `next` 16.1.6 → **16.2.9** (latest stable; `package.json` floor `^16.1.6`→`^16.2.9`) — vyčistilo **původní HIGH next advisories** (HTTP request smuggling v rewrites, Server Actions CSRF přes null origin, DoS Server Components).
  • • **Zbývá 2 moderate** (next + jeho bundled `postcss`, GHSA-qx2v-qp2m-jg93) — fix je až **next ≥16.3.0**, který existuje jen jako **canary**. `npm audit fix --force` by degradoval next na 9.3.3 (breaking) → NEPOUŽITO. Část advisories navíc neplatí (projekt nemá `middleware.ts`, `unoptimized:true`).
  • • **Tracked post-launch**: upgrade next na ≥16.3.0 až vyjde stable.
  • • Verifikace: `tsc` ✓ (pozn.: tsc musí běžet po `next build` — jinak kontroluje zastaralé `.next/types`), 189 testů ✓, `next build` ✓. Změna jen `package.json` + `package-lock.json`.
  • Pre-launch audit HIGH — Skupina 5: SMS rate limit (H-B)
  • • Nová tabulka `sms_attempts (user_id, phone, kind, attempted_at)`, RLS enabled BEZ policies (jen service_role) + 2 indexy + `cleanup_sms_attempts()` CRON denně 4:15 UTC. Migrace `20260611160155_sms_attempts_rate_limit.sql`.
  • • Edge Function `send-verification-sms` v13→v14:
  • • Ověřeno na živé DB: tabulka + RLS bez policies + CRON zaregistrovány; count logika (cooldown/24h okno) sedí. Žádné frontend změny.
  • Pre-launch audit HIGH — Skupina 4: GDPR výmaz adres + fotek (H-F)
  • • **Mazat (adresa + fotky) JEN leady BEZ matche** — čistá PII bez zakázky. Matchnuté leady (jakýkoli stav vč. `watching`) zůstávají KOMPLETNÍ: důkaz pro vymáhání provize + watchdog potřebuje `cadastral_code`/`parcel_number`.
  • • **Watching matche se ZÁMĚRNĚ neruší** (dřív krok 7d je cancelnul) — jinak by se zabil watchdog a při prodeji po smazání by platforma přišla o provizi. Nárok je vůči makléři (anonymizace sellera nevadí, identita v `*_snapshot`).
  • • Fotky se mažou per-lead z polí `photos_interior`/`photos_exterior` (split na `/property-photos/`) → ve sdílené složce `${userId}/temp/` zůstanou fotky matchnutých leadů nedotčené (NE prefix-list).
  • • Anonymizace: `exact_address=""`, `street/zip/lat/long/display_*/cadastral_*/parcel/house/lv/unit=null`, photos `[]`. service_role obejde RLS + H-D trigger.
  • • Edge Function `delete-account` v18→v19 (CLI). Response + log doplněn o `leads_anonymized`/`photos_deleted`. Ověřeno na živé DB (transakce + rollback): seller se 3 leady (1 nematchnutý + 2 matchnuté) → smazán jen nematchnutý (`exact_address=''`), matchnuté nedotčené vč. `cadastral_code` (watchdog OK). Žádné frontend změny.
  • Pre-launch audit HIGH — Skupina 3: Anonymizace nabídek před matchem (H-E)
  • • **`lead_bid_count(p_lead_id)`** — SECURITY DEFINER počítadlo (obchází RLS; naivní `count(*)` uvnitř bids policy = rekurze). Použito v policy i jako RPC pro lock screen.
  • • **`bids` SELECT policy** — seller větev gated revealem: `status='matched' OR lead_bid_count(lead_id) >= max_bids`. Před revealem seller nevidí žádný bid ani přímým REST.
  • • **`profiles` SELECT policy** — nahrazena klauzule „seller vidí bidujícího makléře" za „seller vidí **matchnutého** makléře" (matches-join). Před matchem profil makléře nedostupný → embed `broker` null.
  • • Frontend: `use-lead-detail.ts` (RPC `lead_bid_count` → `bidCount`, reveal/lock z bidCount místo `bids.length`), `section-bids.tsx` (empty/lock/reveal podle bidCount), `BidWithBroker.broker` nullable, `bid-comparison-table` + `section-match-info` null-guard (identita jen post-match, jinak „#N"). Tabulka už labelovala `#N`.
  • • Migrace `20260611142241_anonymize_bids_and_broker_pre_match.sql`. Ověřeno na živé DB (`SET ROLE authenticated` + rollback): pre-reveal seller vidí 0 bidů (count RPC vrací správně), po revealu vidí bidy, a z 5 bidujících vidí profil JEN matchnutých (nematchnutí skrytí). tsc + 189 testů + build ✓.
  • Pre-launch audit HIGH — Skupina 2: Leads column-lock (H-D)
  • • Oprava: trigger `protect_lead_privileged_columns` (BEFORE INSERT OR UPDATE). Admin (`is_admin`) a backend (`auth.uid() NULL`) bypass.
  • • Migrace `20260611140728_protect_lead_privileged_columns.sql`. Ověřeno na živé DB (transakce s rollbackem): UPDATE `pending_review→active`+`max_bids=99` blokováno; cancel/extend/resubmit prošly; resubmit deadline přepsán z minulosti na now+24h; INSERT `status='active'`/`max_bids=99` vznikl jako `pending_review`/5.
  • Pre-launch audit HIGH — Skupina 1: SECURITY DEFINER funkce + emaily (H-A + H-C)
  • • Oprava funkce: guard `IF auth.uid() IS NOT NULL AND NOT is_admin(auth.uid())` — povolí service_role/CRON (`uid NULL`) i admina, blokuje běžného uživatele. Param sjednocen na `user_id`.
  • • Callery sjednoceny na `user_id`: checkin-send (×6), expire-leads (×2), watchdog-check (×1), confirm-contract (×2), confirm-sale-seller (×1), report-sale (×1), email-action (×3).
  • • Ověřeno na živé DB: pod service_role kontextem vrací email.
  • • Oprava: guard `IF pg_trigger_depth() = 0 AND auth.uid() IS NOT NULL AND auth.uid() <> p_user_id AND NOT is_admin(auth.uid()) THEN RAISE`. `pg_trigger_depth()>0` zajistí, že SQL trigger dual-writes (`notify_new_bid`, `notify_match_created`, ...) projdou — jinak by se rozbil INSERT bidu/matche. service_role/CRON (`uid NULL`), admin a self projdou.
  • • Cross-user volání z API routes přepnuta na service_role: `confirm-contract`, `confirm-sale-seller`, `report-sale`, `submit-rating` (nový `supabaseAdmin`), `lib/checkin-utils.ts`. Vedlejší přínos: admin notifikace v těchto routes dřív nefungovaly vůbec (seller/broker přes RLS nepřečte admin profily) — nyní fungují.
  • • Ověřeno na živé DB (transakce s rollbackem): přímá cross-user injekce blokována, volání z trigger kontextu prošlo (dual-write bezpečný).
  • • Migrace `20260611133822_harden_definer_funcs_email_and_notification.sql`. Redeploy Edge Functions: `checkin-send` (v12, MCP), `expire-leads` + `watchdog-check` (CLI). Verifikace: tsc + 189 testů + eslint + build.
  • Pre-launch audit: oprava kritických blokerů (C1–C4 + matches RLS)
  • • Migrace `20260611103658_fix_handle_new_user_role_whitelist.sql`
  • • Migrace `20260611103709_protect_profile_privileged_columns.sql`
  • • Migrace `20260611103724_anonymize_leads_view_definer.sql`
  • • **Akceptovaný tradeoff:** vrací 1 Supabase advisor `security_definer_view` (ERROR) — vědomě, jde o záměrný anonymizační boundary.
  • • Migrace `20260611103734_fix_cleanup_unverified_condition.sql`
  • • Migrace `20260611103742_matches_update_admin_only.sql`
  • • Routes: `report-sale`, `confirm-contract`, `confirm-sale-seller`, `lib/checkin-utils.ts` — zápis do `matches` přes service_role po server-side autorizaci.
9. května 2026
  • Cleanup: odstranění staging password feature
  • • `frontend/src/app/auth/staging/page.tsx` — login formulář
  • • `frontend/src/app/api/staging-auth/route.ts` — API endpoint nastavující cookie
  • • `stagingAuth` rate limit z `lib/rate-limit.ts`
  • • `CLAUDE.md`, `LEGAL-PRIVACY.md`, `TESTING-MATRIX.md/.csv`, `.claude/docs/ROUTES-AND-STRUCTURE.md`, `.claude/docs/COMPLETED-FEATURES.md` — odstraněny zmínky o staging gate
  • • `LEGAL-PRIVACY.md` — odstraněna deklarace staging cookie ze sekce 9.1 (právní dokument nesmí deklarovat cookie, kterou neukládáme)
19. dubna 2026
  • Security: quick wins z auditu (M2, H4, M4)
  • • **M2 — sendNotification rate limit 20→5/min** (`lib/rate-limit.ts:114`): Admin akce limit zpřísněn (anti-spam). Legitimní use case (admin posílá emaily uživatelům) běží daleko pod limitem.
  • • **H4 — `supabase/config.toml`**: Nový soubor trackuje `verify_jwt` flag pro všech 8 Edge Functions v repo (jediný source of truth, prevence driftu mezi platform UI a kódem). Inventura přes Supabase MCP potvrdila, že stav v UI odpovídá: `send-notification-email/watchdog-check/expire-leads/checkin-send/auto-approve-leads` mají `false` (s vlastním Bearer checkem), `delete-account/send-verification-email/send-verification-sms` mají `true`.
  • • **M4 — anti-bruteforce na `delete-account`**: Self-service mode volal `signInWithPassword()` bez per-IP ochrany → bruteforce risk. DB-backed rate limit (perzistentní napříč Edge Function instances).
  • • **M1 (Mapy.cz error sanitizace) — uzavřeno jako false positive**: Po revizi `address-suggest` a `reverse-geocode` — oba routes vrací generic `apiError(...)` při `!response.ok`. Mapy.cz API key je v URL query, ne v response body, takže `{...data, source}` spread není vektor leaku.
  • Security: oprava 4 nálezů z bezpečnostního auditu (C1, C2, H1, H2)
  • • **C1 — `send-notification-email` Bearer auth check (CRITICAL)**: Edge Function měla `verify_jwt: false` BEZ vlastního auth checku → publicly callable. Kdokoliv s URL mohl posílat emaily z naší domény (`notifikace@spravnymakler.cz`) — riziko spamu/phishingu. Fix:
  • • **C2 — `useAuthGuard` MFA fail-closed (CRITICAL)**: Pokud `getAuthenticatorAssuranceLevel()` nebo `listFactors()` selhalo (síťová chyba, Supabase timeout), kód spadl do `setIsAuthorized(true)` → admin dostal plný přístup BEZ 2FA verifikace. Fix:
  • • **H1 — `/api/send-notification` sanitizace error (HIGH)**: Proxy vracela `errorText` z Edge Function přímo klientovi (`apiError(\`...${errorText}\`, ...)`) → potenciální leak interních detailů Resend/SDK chyb. Fix: generic `"Email se nepodařilo odeslat."` + 502 status, detail jen do `console.error`.
  • • **H2 — `send-notification-email` sanitizace error (HIGH)**: Edge Function vracela `error.message` (Resend) a `String(error)` (catch) v JSON response. Fix: generic `"Email send failed"` (502) a `"Internal server error"` (500), detail jen do `console.error`.
  • Refactor: property-params-section split + memoizace
  • • Původní monolitický `property-params-section.tsx` (543 ř.) rozdělen do `property-params-section/`:
  • • `activeAmenities`, `activeHouseChars`, `unmanagedEntries`, `currentEncumbrances` nyní přes `useMemo` — dříve se přepočítávaly na KAŽDÝ render (včetně každého znaku při psaní do inputu)
  • • `has()` predikát stabilizován přes `useCallback` → umožňuje memoizaci derivovaných seznamů
  • • `setVal` stabilizován přes `useCallback` (setter pattern, nemění se)
  • • Žádná změna UI ani business logiky — čistě interní refactor pro lepší udržovatelnost a render perf
  • Perf: paralelní načítání na stránce hodnocení makléře
  • • `seller/rate/[matchId]` — načtení `matches` detailu a `broker_ratings` existujícího hodnocení nyní přes `Promise.all` místo sekvenčně (oba dotazy klíčují pouze na `matchId` z URL a nezávisí na sobě)
  • • Úspora ~50 ms latence na každém otevření stránky
  • Fix: setTimeout cleanup v auth/profile hooks
  • • `useChangePassword.ts` a `useProfileForm.ts` držely `setTimeout` bez cleanup → pokud uživatel navigoval pryč nebo zavřel dialog během 2-3s okna, React vypisoval "state update on unmounted component" warn + potenciální memory leak
  • • Standardní pattern: `useRef<ReturnType<typeof setTimeout>>` + cleanup v `useEffect` (stejný pattern jako `useNotifications.ts`)
  • Lokalizace Supabase auth chyb do češtiny
  • • Nový helper `lib/utils/auth-errors.ts` — funkce `translateAuthError(err)` mapuje anglické chybové zprávy od Supabase Auth (leaked password, slabé heslo, invalid credentials, rate limit, duplikátní email) na uživatelsky přívětivé české texty
  • • Použito v 4 místech: `useChangePassword.ts` (změna hesla v profilu), `reset-password/page.tsx` (zapomenuté heslo), `use-seller-register.ts` + `use-broker-register.ts` (registrace — detail pro password větev)
  • • Hlavní dopad: při leaked password (HaveIBeenPwned) vidí uživatel "Toto heslo bylo nalezeno v úniku dat nebo je příliš jednoduché. Zvolte prosím jiné." místo anglické "Password is known to be weak and easy to guess..."
  • • Login error zůstává univerzální ("Neplatný email nebo heslo") — security best practice (není žádoucí odhalit, zda email v systému existuje)
  • DB optimalizace: Supabase advisors (M1-M5)
  • • **M1 `security_quick_wins_m1`** — `SET search_path` na 2 funkcích (`update_support_ticket_updated_at`, `notify_admin_pending_review` SECURITY DEFINER), DROP dead-code `insert_service` policy na 3 tabulkách (`notifications`, `checkin_tokens`, `email_action_tokens`)
  • • **M2 `missing_fk_indexes_m2`** — 7 indexů pro unindexed FK: `broker_ratings.moderated_by/seller_id`, `checkin_tokens.user_id`, `leads.reviewed_by`, `profiles.approved_by/suspended_by`, `support_tickets.admin_replied_by`
  • • **M3 `rls_init_plan_fix_m3`** — přepis `audit_logs.admin_read_all` policy: `auth.uid()` → `(SELECT auth.uid())` pro init plan optimalizaci (eval 1× per query místo per řádek)
  • • **M4 `consolidate_rls_policies_m4`** — konsolidace duplicitních permissive policies na 5 tabulkách (16 WARN findings): `broker_ratings` SELECT (3→1), `checkin_tokens`/`email_action_tokens`/`support_tickets` SELECT (2→1), `matches` UPDATE (2→1). Chování je funkčně identické, jen rychlejší.
  • • **M5 `storage_bucket_listing_m5`** — DROP dead-code SELECT policies `Anyone can view photos` (property-photos) a `public_read_brand_assets` — ověřeno, že v projektu není ani jedno volání `.list()` nebo `.createSignedUrl()`. Public URL access funguje přes `bucket.public=true` flag, ne policy.
16. dubna 2026
  • Bugfix: Terms re-acceptance loop
  • • **Opraveno**: Po odsouhlasení aktualizovaných podmínek (broker) se stránka zobrazila znovu místo přesměrování na dashboard — chyběl `refetchProfile()` po DB updatu, `useAuthGuard` viděl stale `terms_version` a redirectoval zpět
  • Reset verzí podmínek na 0.5
  • • **Verze podmínek**: Změna z `2.0` na `0.5` (produkční bude `1.0`) v `terms-config.ts`
  • • **DB reset**: Všichni uživatelé nastaveni na `terms_version = '0.4'` → vyžadována re-akceptace při příštím přihlášení
  • • **Seller blocking redirect**: `useAuthGuard` nyní redirectuje i sellery na `/auth/accept-terms` při outdated podmínkách (dříve jen non-blocking banner, který nebyl nikde renderovaný)
11. dubna 2026
  • Legal compliance: GDPR a právní dokumenty
  • • **Seller consent tracking**: Přidán povinný checkbox souhlasu s podmínkami při registraci sellera + ukládání `terms_accepted_at` a `terms_version` do DB
  • • **Google OAuth accept-terms**: Nová stránka `/auth/accept-terms` — OAuth selleři musí odsouhlasit podmínky po prvním přihlášení
  • • **Terms re-acceptance flow**: `useAuthGuard` kontroluje verzi podmínek — broker = blocking redirect, seller = non-blocking banner (`terms-reaccept-banner.tsx`)
  • • **Sdílená verze podmínek**: `lib/terms-config.ts` — single source of truth pro verze (seller + broker)
  • • **Site-wide footer**: Nová sdílená komponenta `footer.tsx` s právními odkazy (Podmínky, Ochrana soukromí, Kontakt) — přidán do root layout, odstraněn duplicitní inline footer z landing page
  • • **Email footer legal links**: Přidány odkazy na Podmínky a Ochranu soukromí do patičky všech emailů (`brand.ts`)
  • • **Contact form privacy notice**: Informace o zpracování osobních údajů a 3-leté retenci před submit tlačítkem
  • • **EXIF metadata stripping**: Fotky < 300KB nyní procházejí canvas redraw (strip GPS/EXIF dat) místo přeskakování komprese
  • • **Data export (GDPR čl. 20)**: Nová karta "Export dat" v profilu s mailto odkazem pro žádost o přenositelnost údajů
  • Pre-launch audit: P1 opravy
  • • Přidáno `error.tsx` — uživatelsky přívětivá error boundary s Sentry + "Zkusit znovu" tlačítkem
  • • Přidáno `not-found.tsx` — brand 404 stránka s odkazem na hlavní stránku
  • • Opraveno: `handleDeleteLead` v admin lead detail — přidán chybějící error check na mazání nabídek
  • • Nahrazeno 4x `<img>` za Next.js `<Image>` komponentu (MFA loga, admin property photos)
  • • Vyčištěno 8 ESLint warnings (nepoužívané importy/proměnné z refaktoringu) — 0 warnings
  • Pre-launch audit: P3 vylepšení
  • • Přidáno `aria-label` na 4 icon-only tlačítka (photo-lightbox: zavřít, předchozí, další; notifications: zpět)
  • • Přidáno page-level metadata na 8 veřejných stránek (kontakt, login, registrace seller/broker, registrace, podmínky, podmínky broker, ochrana údajů)
  • Pre-launch audit: P2 opravy
  • • Opraveno N+1 admin email notifikace v `confirm-contract` a `report-sale` — sekvenční loop nahrazen `Promise.all()` batch fetchem
  • • Přidáno Sentry error tracking na fire-and-forget notifikace ve 4 API routes (`confirm-contract`, `report-sale`, `submit-rating`, `support-ticket`)
9. dubna 2026
  • Refactor: auth/register/seller — rozpad na hook + sekce (konzistence s broker)
  • • `page.tsx` zmenšen z 461 na 56 řádků (orchestrátor)
  • • `use-seller-register.ts` (123 ř.) — hook: formData, validace, handleSubmit, redirect
  • • `section-form.tsx` (83 ř.) — formulář: Google OAuth, inputs, submit
  • • `section-terms-modals.tsx` (96 ř.) — Terms + Privacy modální dialogy
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: admin/bids list page — rozpad na hook + sekce
  • • `page.tsx` zmenšen z 366 na 57 řádků
  • • `use-bids.ts` (103 ř.) + `section-table.tsx` (115 ř.) + `types.ts` (38 ř.)
  • Refactor: admin/users list page — rozpad na hook + sekce
  • • `page.tsx` zmenšen z 366 na 69 řádků
  • • `use-users.ts` (105 ř.) + `section-table.tsx` (113 ř.) + `types.ts` (34 ř.)
  • Refactor: admin/leads list page — rozpad na hook + sekce
  • • `page.tsx` zmenšen z 383 na 90 řádků (orchestrátor)
  • • `use-leads.ts` (105 ř.) + `section-table.tsx` (116 ř.) + `types.ts` (29 ř.)
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: admin/ratings page — rozpad na hook + sekce
  • • `page.tsx` zmenšen z 450 na 89 řádků (orchestrátor + Suspense wrapper)
  • • `use-ratings.ts` (136 ř.) — hook: fetch, filter, expand, handleModerate, deep-link
  • • `section-table.tsx` (148 ř.) — tabulka s expandable rows (star ratings, detail) + moderace
  • • `types.ts` (50 ř.) — RatingRow, lookupLabel, CSV_COLUMNS
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: admin/support page — rozpad na hook + sekce + konstanty
  • • `page.tsx` zmenšen z 575 na 90 řádků (orchestrátor + Suspense wrapper)
  • • `use-support.ts` (183 ř.) — hook: fetch, filtry, expand, handleReply, deep-link
  • • `support-constants.ts` (20 ř.) — STATUS_CONFIG, CATEGORY_CONFIG, STATUS_OPTIONS
  • • `section-filters.tsx` (69 ř.) — search + status/category selects
  • • `section-table.tsx` (177 ř.) — tabulka s expandable rows + reply form + pagination
  • • `types.ts` (31 ř.) — SupportTicket, CSV_COLUMNS
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: admin/networks page — rozpad na hook + sekce + dialog
  • • `page.tsx` zmenšen z 582 na 65 řádků (orchestrátor)
  • • `use-networks.ts` (244 ř.) — hook: fetch, CRUD, approval workflow, slug gen
  • • `section-pending.tsx` (152 ř.) — pending approvals tabulka + approve dialog
  • • `section-networks-table.tsx` (72 ř.) — hlavní tabulka sítí
  • • `add-network-dialog.tsx` (81 ř.) — dialog pro přidání sítě
  • • `types.ts` (4 ř.) — ProfileWithCustomNetwork
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: admin/matches list page — rozpad na hook + sekce + konstanty
  • • `page.tsx` zmenšen z 703 na 100 řádků (orchestrátor)
  • • `use-matches.ts` (203 ř.) — hook: fetch, filtrování, sorting, handleStatusChange (state machine)
  • • `matches-constants.ts` (29 ř.) — MATCH_STATUS_CONFIG, VALID_TRANSITIONS
  • • `section-status-tabs.tsx` (52 ř.) — status filter taby s počty a ikonami
  • • `section-table.tsx` (231 ř.) — tabulka s 11 sloupci, inline status select, pagination
  • • `types.ts` (60 ř.) — Match, MatchStatus, CSV_COLUMNS
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: admin/audit page — rozpad na hook + sekce + konstanty
  • • `page.tsx` zmenšen z 737 na 63 řádků (orchestrátor)
  • • `use-audit-log.ts` (246 ř.) — hook: fetch, filtrování, sorting, pagination, entity cache
  • • `audit-constants.ts` (147 ř.) — ACTION_LABELS, ENTITY_LABELS, ROLE_BADGE_CONFIG, METADATA_KEY_LABELS, formattery
  • • `section-filters.tsx` (99 ř.) — filter bar: search, dropdowns, date range
  • • `section-table.tsx` (221 ř.) — tabulka: header, expandovatelné řádky, detail, pagination, CSV export
  • • `types.ts` (35 ř.) — AuditLog, EntityField, EntityCacheEntry, CSV_COLUMNS
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: auth/verify page — rozpad na hook + sekce
  • • `page.tsx` zmenšen z 552 na 126 řádků (orchestrátor)
  • • `use-verify.ts` (366 ř.) — hook: 13 stavů, 5 efektů, 8 handlerů (email, SMS, phone, redirect)
  • • `section-email.tsx` (47 ř.) — email verifikace karta
  • • `section-phone.tsx` (130 ř.) — phone verifikace: OTP flow + phone input (Google OAuth)
  • • `types.ts` (35 ř.) — EmailSectionProps, PhoneSectionProps
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: bid-comparison-table — rozpad na adresář s moduly
  • • `bid-comparison-table.tsx` (652 ř.) → adresář `bid-comparison-table/` se 6 soubory
  • • `index.tsx` (318 ř.) — hlavní JSX komponenta (tabulka, header, body, footer)
  • • `use-bid-comparison.ts` (167 ř.) — hook: state, handlers, memos (rozdíly, filtrování, eliminace)
  • • `collapsible-category.tsx` (84 ř.) — sbalitelná sekce kategorií služeb
  • • `service-row.tsx` (32 ř.) — řádek služby s check/cross ikonou
  • • `text-cell-popover.tsx` (53 ř.) — text s popoverem pro dlouhý obsah
  • • `types.ts` (15 ř.) — BidComparisonTableProps, CategoryKey, ServiceItem
  • • Import path zachován (`@/components/bid-comparison-table`)
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: checkin-send Edge Function — rozpad na moduly
  • • `index.ts` zmenšen z 461 na 68 řádků (auth + orchestrátor)
  • • `helpers.ts` (86 ř.) — sdílené: NotifPrefs typ, getNotifPref, sendEmailViaBrand, createActionToken, parseMatchRelations
  • • `contract-prompt.ts` (106 ř.) — Fáze A: "Podepsali jste smlouvu?" (7 dní po matchi)
  • • `contract-reminder.ts` (106 ř.) — Fáze B: Připomínka + admin alert (21 dní)
  • • `sale-check.ts` (150 ř.) — Fáze C+D: Sale check (42 dní) + opakovaný (3 měsíce)
  • • Žádná změna chování — čistě DX refaktorizace
  • Refactor: send-notification-email Edge Function — rozpad na moduly
  • • `index.ts` zmenšen z 807 na 99 řádků (HTTP handler only)
  • • 3 infrastrukturní moduly: `types.ts` (EmailType, EmailPayload, TemplateMap), `vocative.ts` (vokativ), `brand.ts` (SITE_URL, BRAND, header/footer/infobox/button)
  • • 7 template souborů v `templates/`: `lead.ts`, `match.ts`, `sale.ts`, `broker.ts`, `checkin.ts`, `contract.ts`, `watchdog.ts`
  • • Barrel `templates/index.ts` — merge všech 23 šablon do jednoho Record
  • • Přidán chybějící `action_token` do `EmailPayload` typu (type-only fix)
  • • Žádná změna chování ani email outputu — čistě DX refaktorizace
  • Refactor: New lead wizard step 3 — rozpad na params sub-komponenty
  • • `seller/new-lead/step-3-parameters.tsx` zmenšen z 901 na ~85 řádků (orchestrátor)
  • • 6 sub-komponent: `params-apartment`, `params-house`, `params-commercial`, `params-common`, `params-amenities`, `params-encumbrances`
  • • `CheckboxItem` helper v params-amenities, `EncumbranceGroup` helper v params-encumbrances (eliminace repetitivního JSX)
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: Broker lead detail — rozpad na sekce a hook
  • • `broker/leads/[id]/page.tsx` zmenšen z 892 na ~50 řádků (orchestrátor)
  • • 2 section komponenty: `section-lead-info` (property details + photos + map), `section-bid-panel` (formulář/status + confirm dialog)
  • • Centrální hook `use-broker-lead-detail.ts` (10 state, 1 efekt, 6 handlerů)
  • • Sdílené typy v `types.ts` (BrokerLead, ExistingBid, BidFormData, section Props)
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: Broker match detail — rozpad na sekce a hook
  • • `broker/matches/[id]/page.tsx` zmenšen z 970 na ~60 řádků (orchestrátor)
  • • 5 section komponent: `section-header`, `section-seller-contact`, `section-cooperation`, `section-property-details`, `section-bid`
  • • Centrální hook `use-broker-match-detail.ts` (10 state, 1 efekt, 4 handlery)
  • • Sdílené typy v `types.ts` (MatchDetail, section Props)
  • • `ContractConfirmButton` extrahován jako reusable sub-komponenta (eliminace duplikace AlertDialog)
  • • `PropertyRow` helper v section-property-details (eliminace opakovaného JSX)
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: Broker register page — rozpad na sekce a hook
  • • `auth/register/broker/page.tsx` zmenšen z 1064 na ~113 řádků (orchestrátor)
  • • 5 section komponent: `section-personal-info`, `section-company-info`, `section-network`, `section-credentials`, `section-terms`
  • • Centrální hook `use-broker-register.ts` (15 state, 3 efekty, 10 handlerů)
  • • Sdílené typy v `types.ts` (BrokerFormData, IcoVerificationState, section Props)
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
8. dubna 2026
  • Refactor: Admin user detail — rozpad na sekce a hook
  • • `admin/users/[id]/page.tsx` zmenšen z 1137 na ~130 řádků (orchestrátor)
  • • 4 section komponenty: `section-header`, `section-user-info` (2 exporty), `section-broker-prefs`, `section-data-tables`
  • • Centrální hook `use-admin-user-detail.tsx` (13 state, fetch, 8 handlerů)
  • • Sdílené typy v `types.ts` (UserDetail, section Props)
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: Admin lead detail — rozpad na sekce a hook
  • • `admin/leads/[id]/page.tsx` zmenšen z 1187 na ~130 řádků (orchestrátor)
  • • 3 section komponenty: `lead-header-section`, `property-info-section`, `bids-table-section`
  • • Centrální hook `use-admin-lead-detail.tsx` (10 state, fetch, 15 handlerů)
  • • Section Props přidány do existujícího `lead-detail-types.ts`
  • • Existující `property-details-section` a `property-params-section` beze změn
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: Seller lead detail — rozpad na sekce a hook
  • • `seller/leads/[id]/page.tsx` zmenšen z 1254 na ~110 řádků (orchestrátor)
  • • 6 section komponent: `section-banners`, `section-match-info`, `section-collaboration`, `section-bids`, `section-lead-detail`, `section-actions`
  • • Centrální hook `use-lead-detail.ts` (18 state, fetch, 5 handlerů, derived values)
  • • Sdílené typy v `types.ts` (SellerLead, section Props)
  • • Deduplikovaný ContractConfirmButton (dříve 2x duplikovaný AlertDialog)
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: Seller lead edit — rozpad na field komponenty a hook
  • • `seller/leads/[id]/edit/page.tsx` zmenšen z 1256 na ~110 řádků (orchestrátor)
  • • 5 field komponent: `fields-apartment`, `fields-house`, `fields-commercial`, `fields-land-other`, `fields-shared`
  • • Centrální hook `use-edit-lead.ts` (state, fetch, performUpdate, handleSubmit)
  • • Sdílené typy v `types.ts` (SellerLead, EditFormData, INITIAL_EDIT_FORM, EditSectionProps)
  • • 3x duplikovaný inline PENB select nahrazen existující `<EnergyRatingSelect>` komponentou
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: Admin match detail — rozpad na komponenty a hook
  • • `admin/matches/[id]/page.tsx` zmenšen z 1493 na ~140 řádků (orchestrátor)
  • • 8 section komponent: `section-timeline`, `section-property`, `section-parties`, `section-broker-offer`, `section-sale`, `section-invoice`, `section-checkin`, `section-actions`
  • • Centrální hook `use-match-detail.ts` (state, fetch, 8 handlerů, email funkce)
  • • Sdílené typy v `types.ts`, business konstanty v `constants.ts`
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
  • Refactor: Profil stránka — rozpad na komponenty a hooky
  • • `profile/page.tsx` zmenšen z 2124 na 206 řádků (orchestrátor)
  • • 5 tab komponent: `tab-profile`, `tab-company`, `tab-notifications`, `tab-templates`, `tab-account`
  • • 5 custom hooků: `useProfileForm`, `useIcoVerification`, `useServiceProfiles`, `useChangePassword`, `useDeleteAccount`
  • • `useIcoVerification` deduplikuje dříve duplicitní kód (vlastní IČO + parent IČO)
  • • Utility `notification-prefs.ts` (getPref/setPref/deriveNotifyNewLeads)
  • • `CZECH_REGIONS` přesunuto do sdílených `lib/constants.ts`
  • • Nový `profile/types.ts` se sdílenými typy a props interfaces
  • • Žádná změna designu ani funkčnosti — čistě DX refaktorizace
7. dubna 2026
  • Feature: Email-driven match lifecycle (1-click tokeny + sale check)
  • • Nový systém řídí celý lifecycle matche přes email — seller nemusí na web
  • • 1-click potvrzení z emailu přes token (tabulka `email_action_tokens`, endpoint `/api/email-action`)
  • • Potvrzovací stránka `/email-action` s ochranou proti náhodnému kliknutí (Card + "Ano, potvrzuji")
  • • Fáze A: Contract prompt 7 dní po matchi (1-click token CTA místo odkazu na web)
  • • Fáze B: Contract reminder 21 dní po matchi + admin notifikace pokud nikdo nepotvrdil
  • • Fáze C: Sale check 42 dní (6 týdnů) po `contracted` — "Došlo k prodeji?"
  • • Fáze D: Opakovaný sale check každé 3 měsíce
  • • Nové email šablony: `contract_reminder`, `sale_check`
  • • Nový notifikační typ `cooperation` (smlouva + prodej) v profilu — nahrazuje `checkin`
  • • Starý check-in systém (Probíhá/Dokončeno/Problém) odstraněn — CheckinBanner ze seller/broker detailů
  • • `checkin-send` kompletně přepsán na v8 (match lifecycle)
  • • `send-notification-email` v21 (2 nové šablony + token CTA)
  • Feature: Nový stav matche `contracted` (Spolupráce zahájena)
  • • Nový stav mezi `watching` a `sale_reported` — indikuje podepsanou zprostředkovatelskou smlouvu
  • • Automatický přechod: broker potvrdí podpis smlouvy → stav `watching` → `contracted`
  • • Seller sám nestačí — čeká se na makléře (makléř by šel proti sobě, kdyby lhal)
  • • `contracted` NENÍ prerekvizita prodeje — broker může nahlásit prodej přímo z `watching`
  • • Admin matches list: nový tab "Spolupráce zahájena" s počítadlem
  • • Admin match detail: nový krok v timeline mezi "Spárováno" a "Nahlášeno"
  • • Watchdog (katastr) a check-in běží i ve stavu `contracted`
  • • DB migrace: rozšířen CHECK constraint na `matches.status`
  • • Upravené API routes: `confirm-contract`, `report-sale`, `confirm-sale-seller`
  • • Deploy Edge Functions: `checkin-send` v10, `watchdog-check` v16
  • Fix: Konzistentní mezery mezi kartami na seller/broker match detailu
  • • Sjednoceny margin-bottom na `mb-6` u všech Card sekcí na seller lead detailu (bylo mb-4/mb-6/mb-8)
  • • Sjednoceny margin-bottom na `mb-6` u všech Card sekcí na broker match detailu (bylo mb-8)
6. dubna 2026
  • UX: Broker match detail — kompletní info o nemovitosti + sbalitelné sekce
  • • Detail nemovitosti nyní zobrazuje všechny parametry (dispozice, stav, PENB, omezení, příslušenství, house features, rok kolaudace/rekonstrukce) — stejné info jako při dávání nabídky
  • • Exteriérové fotografie odhaleny po matchi (nově přidáno k interiérovým)
  • • Fotografie používají PhotoGallery komponentu s lightboxem (místo základního img gridu)
  • • Sekce nabídky zobrazuje kompletní detail: služby podle kategorií s checkmarky (readonly ServiceCheckboxes), další služby, zprávu, datum odeslání
  • • Opravena duplicitní přesná adresa (exact_address a street/zip/city zobrazovaly totéž)
  • • Obě sekce (nemovitost + nabídka) jsou sbalovací — nemovitost defaultně rozbalená, nabídka defaultně sbalená
  • • Collapsible trigger s ChevronDown animací a textem Zobrazit/Skrýt (konzistentní se seller detailem)
  • • Hero banner: přidán typ nemovitosti + lokace + plocha (např. "Byt - Plzeň - Bolevec 85 m²")
  • • Karta nemovitosti: popis změněn na "Podrobné informace o nemovitosti"
  • • Přijaté nabídky na `/broker/bids`: karta zobrazuje přesnou adresu místo anonymní lokality (po matchi)
  • • Hero banner match detailu: typ + přesná adresa (bez m², ty jsou v podrobnostech)
  • • Karta nemovitosti: zjednodušený nadpis "Podrobné informace o nemovitosti" (typ + adresa je v hero)
  • • Sjednoceno zarovnání collapsible CardHeaders — CardAction slot místo flex-row override + text-left fix na trigger buttons (seller i broker, 4 místa)
  • • AlertDialog potvrzení pro "Potvrdit podpis smlouvy" — ochrana proti náhodnému dotyku (seller i broker, 4 místa)
  • UX: Seller lead detail — zarovnání a sbalitelné nabídky
  • • Zarovnání tlačítka "Ano, prodej proběhl" na úroveň inputu prodejní ceny (fix `flex items-start`)
  • • Nabídky makléřů u matched leadu jsou nyní defaultně sbalené, rozbalí se kliknutím na Card trigger s textem "Zobrazit nabídky"
  • • Detail poptávky u matched leadu také defaultně sbalený — trigger je přímo v CardHeader (jedna Card, ne dva oddělené boxy), u ostatních stavů vždy rozbalený
  • • Sloučení sekcí smlouvy a prodeje do jedné Card (seller i broker) — Separator mezi sekcemi
  • • Redesign Card "Stav spolupráce" — CardHeader s nadpisem, sekční nadpisy, info-box styl na stavových zprávách (seller i broker)
  • • Vertikální layout prodeje (input + tlačítko pod sebou, konzistentně se smlouvou)
  • • Sjednocení collapsible triggerů — nabídky i detail poptávky používají CardTitle + CardDescription (stejná velikost, výška)
  • Feature: Potvrzení smlouvy a prodeje — obě strany
  • • Seller i broker mohou nezávisle potvrdit podpis zprostředkovatelské smlouvy (nová UI sekce na detailu leadu/matche)
  • • Seller může potvrdit prodej nemovitosti (paralelně k broker report-sale, volitelná cena)
  • • 4 stavy smlouvy: nikdo nepotvrdil → jedna strana → obě strany → success
  • • Automatický email "Podepsali jste smlouvu?" 7 dní po matchi (CRON Fáze D v checkin-send v6)
  • • 4 nové email šablony: `contract_prompt`, `contract_signed_notification`, `contract_both_signed`, `seller_confirmed_sale`
  • • 2 nové API endpointy: `/api/confirm-contract`, `/api/confirm-sale-seller` (Bearer JWT auth, rate limit, audit log)
  • • Admin match detail: zobrazení stavu smlouvy a seller potvrzení prodeje
  • • DB migrace: 5 nových sloupců v `matches` (contract_signed_by_seller_at, contract_signed_by_broker_at, seller_confirmed_sale_at, seller_confirmed_sale_price, contract_prompt_sent_at)
  • Notifikace: Sjednocení check-in emailů do brand design systému
  • • Check-in emaily (broker, seller, reminder, red flag) nyní používají stejný brand design jako ostatních 13 typů emailů
  • • Přidány 4 nové šablony do `send-notification-email` v19: `checkin_broker`, `checkin_seller`, `checkin_reminder`, `checkin_red_flag`
  • • Nový helper `checkinButtons()` — 3 CTA tlačítka (Probíhá/Dokončeno/Problém) v brand paletě
  • • Refaktorován `checkin-send` v5 — emaily posílány přes `send-notification-email` (místo přímého Resend API)
  • • Opravena chybějící diakritika v in-app notifikacích check-in systému ("Pripominame" → "Připomínáme")
  • • Zdrojový kód `checkin-send` přidán do lokálního repo (`supabase/functions/checkin-send/index.ts`)
  • UX: Sjednocení stylu placeholderů napříč webem
  • • Přidána kurzíva (`italic`) pro všechny placeholdery — centrálně v Input, Textarea, Select a LocationFilter komponentách
  • • Prefix "např." u 25 placeholderů, které vypadaly jako reálná data (jméno, telefon, IČO, bank. účet, cena, provize)
  • • Dotčené stránky: registrace, profil, kontakt, broker nabídka/match, admin match/networks
  • • Auth emaily (login, registrace, forgot-password) ponechány bez "např." — standardní UX pattern
  • UX: Kontaktní údaje po matchi — email + telefon pro obě strany
  • • Prodávající nyní vidí telefon a email makléře v kontaktní kartě po spárování
  • • Makléř nyní vidí email prodávajícího vedle telefonu v kontaktní kartě
  • • Nová RPC funkce `get_match_contact_email(match_id)` — bezpečně vrací email protistrany (SECURITY DEFINER, ověření účasti v matchi)
  • • Sjednocený design kontaktních karet (seller i broker): bg-[#f5f0ff], label-above-value grid, klikací tel:/mailto: linky
  • Fix: Chybějící diakritika v in-app notifikacích
  • • Opravena chybějící česká diakritika v 10 textových řetězcích notifikací (SQL triggery + Edge Function)
  • • Postižené notifikace: bids_ready, new_lead, match (seller i broker), lead_approved (auto-approve)
  • • Nová migrace `20260406_fix_notification_diacritics.sql` — 3x `CREATE OR REPLACE FUNCTION`
  • • Edge Function `auto-approve-leads` v7 — opraveny 2 textové řetězce
  • • Retroaktivně opraveny existující záznamy v tabulce `notifications`
5. dubna 2026
  • Security: HTML injection fix + rate limiting
  • • Přidána `escapeHtml()` utilita — všechny uživatelské vstupy v support ticket emailech jsou nyní escapovány
  • • Přidán rate limit na `/api/staging-auth` (5/min, brute-force ochrana)
  • • Opravena špatná rate limit konstanta na `/api/send-notification` (reverseGeocode → sendNotification)
  • Fix: SQL trigger funkce — source control + deprecated GUC
  • • 4 notification trigger funkce (notify_brokers_new_lead, notify_new_bid, notify_match_created, notify_admin_pending_review) + 6 triggerů nyní trackováno v migracích
  • • `notify_new_bid` a `notify_match_created` opraveny: odstraněn deprecated `current_setting()`, vault-only pattern
  • Refactor: Kódová konzistence
  • • Extrahována duplicitní `evaluateCheckinResponses()` do sdíleného `lib/checkin-utils.ts` s proper SupabaseClient typem
  • • 16x `alert()` nahrazeno `toast.error()` ve 3 admin stránkách (users, leads, networks)
  • • 14x `toLocaleDateString("cs-CZ")` nahrazeno `formatDate()` utilitou v 9 souborech
  • UX: Placeholdery ve formuláři poptávky
  • • Číselné placeholdery ve Step 3 (Parametry) dostaly prefix "např." — plocha, patro, celkem pater, počet místností
  • • Předchozí placeholdery ("3", "5", "75") vypadaly jako vyplněná data
4. dubna 2026
  • Feature: Schvalování registrace makléřů
  • • Nové sloupce v `profiles`: `approved_at`, `approved_by`, `registration_rejection_reason`
  • • CHECK constraint rozšířen o `pending_approval` a `rejected`
  • • `handle_new_user()` — brokeři dostanou `status = 'pending_approval'`
  • • Trigger `notify_admin_pending_broker()` — in-app notifikace adminům
  • • SQL funkce `cleanup_rejected_brokers()` + CRON denně 3:00 UTC
  • • Nová stránka `/auth/pending-approval` — čekací/zamítnutá stránka s pollingem (30s)
  • • `useAuthGuard` — KONTROLA 4.5: redirect pending/rejected brokerů
  • • `/auth/verify` — fix double-redirect pro pending brokery
  • • `/auth/register/broker` — info text "Registrace podléhá schválení"
  • • `/admin/users/[id]` — tlačítka Schválit/Zamítnout, info karta, dual-write notifikace
  • • `/admin/users` — filtr "Ke schválení" + "Zamítnutí", status badge
  • • Audit log: `approve_broker`, `reject_broker` akce
  • • `send-notification-email` v18 — nové šablony `broker_approved`, `broker_rejected`
  • • `/api/send-notification` whitelist rozšířen
  • • `supabase/migrations/20260404120000_broker_pending_approval.sql`
  • • `supabase/functions/send-notification-email/index.ts`
  • • `frontend/src/hooks/useAuthGuard.ts`
  • • `frontend/src/app/auth/pending-approval/page.tsx` (NOVÝ)
  • • `frontend/src/app/auth/verify/page.tsx`
  • • `frontend/src/app/auth/register/broker/page.tsx`
  • • `frontend/src/app/admin/users/[id]/page.tsx`
  • • `frontend/src/app/admin/users/page.tsx`
  • • `frontend/src/app/admin/audit/page.tsx`
  • • `frontend/src/app/api/send-notification/route.ts`
  • • `frontend/src/lib/supabase/types.ts`
  • Fix: Audit log filtry — responsivní layout při středních šířkách
  • • `frontend/src/app/admin/audit/page.tsx` — `lg:grid-cols-5` → `xl:grid-cols-5`, search span na `md:col-span-2`
  • Fix: Audit log — detail pod řádkem + vyhledávání podle e-mailu
  • • Detail se nyní renderuje jako inline řádek přímo pod kliknutým záznamem
  • • Přidáno vyhledávání podle e-mailu (paralelní fetch přes `get_users_with_email` RPC)
  • • E-mail zobrazen pod jménem ve sloupci Uživatel
  • • CSV export rozšířen o sloupec E-mail
  • • `frontend/src/app/admin/audit/page.tsx` — Fragment pattern pro inline detail, email fetch + zobrazení + search
  • Enhance: Audit log — obohacená metadata pro právní důkazy
  • • `create_lead` — přidáno: ulice, PSČ, dispozice, kraj, okres, entity ID
  • • `create_match` — přidáno: město, typ, ulice, jméno makléře, ID makléře, cena nabídky, provize
  • • `extend_lead` — přidáno: město, typ nemovitosti
  • • `submit_bid` — přidáno: město a typ poptávky
  • • `submit_rating` — přidáno: jméno makléře
  • • `approve_lead` / `reject_lead` — přidáno: město, typ, jméno prodávajícího
  • • 7 match akcí (confirm_sale, issue_invoice, record_payment, cancel_match, revert_to_watching, approve/reject_sale_report) — přidáno: jméno makléře/prodávajícího, město, ceny, faktury
  • • `change_match_status` — přidáno: jméno makléře, město
  • • 3 user akce (change_role, suspend, unsuspend) — přidáno: jméno a email cílového uživatele
  • • `moderate_rating` — přidáno: jména makléře/prodávajícího, hodnocení
  • • České popisky metadata klíčů (property_type → "Typ nemovitosti", broker_name → "Makléř" atd.)
  • • Překlad hodnot (typ nemovitosti, ceny formátovány jako Kč)
  • • On-demand entity fetch při rozbalení řádku — kontext z DB pro staré záznamy (lead, match, bid, rating, support_ticket)
  • • Cache pro zamezení opakovaných dotazů
  • • `frontend/src/app/admin/audit/page.tsx` — METADATA_KEY_LABELS, entity cache + fetch, EntityContext sekce
  • • `frontend/src/app/seller/new-lead/page.tsx`, `seller/leads/[id]/page.tsx`, `seller/rate/[matchId]/page.tsx`
  • • `frontend/src/app/broker/leads/[id]/page.tsx`
  • • `frontend/src/app/api/extend-lead/route.ts`, `api/report-sale/route.ts`, `api/submit-rating/route.ts`
  • • `frontend/src/app/admin/leads/[id]/page.tsx`, `admin/matches/[id]/page.tsx`, `admin/matches/page.tsx`
  • • `frontend/src/app/admin/users/[id]/page.tsx`, `admin/ratings/page.tsx`
21. března 2026
  • Fix: Zkrácený nadpis ticketu zůstával oříznutý i po rozbalení (mobil)
  • • `frontend/src/app/support/page.tsx` — podmíněné `truncate` jen pro zavřený stav, rozbalený zobrazí celý nadpis (`whitespace-normal break-words`)
  • Fix: Email notifikace z admin panelu nefungovaly
  • • `lead_approved` email sellerovi při schválení poptávky
  • • `lead_rejected` email sellerovi při zamítnutí poptávky
  • • `sale_confirmed` email makléři při potvrzení prodeje
  • • `rating_request` email sellerovi s žádostí o hodnocení
  • • `frontend/src/app/api/send-notification/route.ts` — nová server-side proxy (auth + admin role check + service_role forwarding)
  • • `frontend/src/app/admin/leads/[id]/page.tsx` — přesměrování na `/api/send-notification`
  • • `frontend/src/app/admin/matches/[id]/page.tsx` — přesměrování na `/api/send-notification`
  • Fix: Přetékání předmětu ticketu na /support (mobil)
  • • `frontend/src/app/support/page.tsx` — přidáno `overflow-hidden` na flex wrapper v `CardHeader`
19. března 2026
  • Chore: GDPR compliance — Sentry sendDefaultPii + Privacy Policy Session Replay
  • • `frontend/sentry.client.config.ts` — přidáno explicitní `sendDefaultPii: false` (potvrzuje default, GDPR best practice)
  • • `frontend/src/app/privacy/page.tsx` — sekce 3.14 doplněna o informaci o Sentry Session Replay (automatické maskování textu a vstupů)
  • Fix: Aktualizace právních dokumentů v registračních modálech na v2.0
  • • Broker: `TERMS_VERSION` 1.0 → 2.0, `EXPLICIT_POINTS` sladěny s čl. 4.3/5/9/14 Rámcové smlouvy v2.0
  • • Broker: Modál smlouvy — odstraněn banner "pracovní verze", nahrazen stručným shrnutím + odkaz na `/terms/broker`
  • • Broker: Modál GDPR — aktualizován na v2.0 shrnutí + odkaz na `/privacy`
  • • Seller: Modál podmínek — aktualizován na v2.0 shrnutí + odkaz na `/terms`
  • • Seller: Modál GDPR — aktualizován na v2.0 shrnutí + odkaz na `/privacy`
  • • `PRAVNIK-BRIEF.md` — aktualizovány reference na verzi 2.0
  • Chore: Odstranění mrtvého kódu
  • • `frontend/src/components/lead-location-map.tsx` — odstraněn nepoužívaný prop `locationArea`
  • • `frontend/src/app/broker/leads/[id]/page.tsx` — odstraněno předávání `locationArea`
  • • `frontend/src/components/service-checkboxes.tsx` — odstraněn mrtvý prop `compact`, zjednodušen className (obě větve ternary byly identické)
  • • `frontend/src/app/globals.css` — smazáno 9 nepoužívaných CSS utility tříd (`border-brand-gradient`, `shadow-brand-glow`, `border-l-brand`, `border-l-brand-dark`, `shadow-brand-sm/md/lg/xl`, `hover-lift`, `active-press`)
  • • `frontend/src/app/profile/page.tsx` — odstraněn deprecated `full_name` zápis
  • • `frontend/src/app/auth/register/seller/page.tsx` — odstraněn deprecated `full_name` zápis
  • • `frontend/src/app/auth/register/broker/page.tsx` — odstraněn deprecated `full_name` zápis
18. března 2026
  • Fix: Horizontální overflow na stránce /support (mobilní verze)
  • • `frontend/src/app/support/page.tsx` — header: responsive stacking (`flex-col` → `sm:flex-row`), ticket karta: badges wrap pod titulek na mobilu
12. března 2026
  • Feat: České skloňování jmen (vokativ) ve všech emailech
  • • `supabase/functions/send-notification-email/index.ts` — v17: přidána `toVocative()` funkce, nové pole `to_first_name`, handler převádí greeting na vokativ
  • • `frontend/src/app/admin/leads/[id]/page.tsx` — 2× přidáno `to_first_name` (lead_approved, lead_rejected)
  • • `frontend/src/app/admin/matches/[id]/page.tsx` — 2× přidáno `to_first_name` (rating_request, sale_confirmed)
  • • `supabase/functions/auto-approve-leads/index.ts` — přidáno `to_first_name` (lead_approved)
  • • `frontend/src/app/api/report-sale/route.ts` — přidáno `to_first_name` (sale_reported)
  • • `frontend/src/app/api/support-ticket/route.ts` — import `toVocative`, oslovení ve vokativu
  • • `frontend/src/app/api/support-ticket/reply/route.ts` — import `toVocative`, oslovení ve vokativu
  • Fix: Validace čísla jednotky (unit_number) — pouze číslice
  • • `frontend/src/components/unit-number-input.tsx` — filtr ne-číslic na onChange, `inputMode="numeric"`, aktualizované hinty
  • • `frontend/src/app/api/cadastral-lookup/route.ts` — API-level strip ne-číslic před parseInt (safety net)
  • • `frontend/src/app/admin/leads/[id]/page.tsx` — stejná validace v admin editaci, opraven placeholder
  • Fix: PriceInput na všech místech zadávání ceny
  • • `frontend/src/app/broker/matches/[id]/page.tsx` — PriceInput pro reportPrice
  • • `frontend/src/app/admin/matches/[id]/page.tsx` — PriceInput pro salePrice
10. března 2026
  • Feature: Edge Function `delete-account` — soft delete uživatelského účtu
  • • Nová Edge Function `delete-account/index.ts` — soft delete (anonymizace profilu + ban ~100 let)
  • • Dva režimy: self-service (uživatel z profilu s ověřením hesla) a admin (z admin panelu bez hesla)
  • • Broker flow: blokace při aktivních matchích (409 ACTIVE_MATCHES), reject pending bidů, smazání service profiles
  • • Seller flow: cancel aktivních leadů + watching matchů, reject pending bidů na těchto leadech
  • • Audit snapshot do `account_deletion_audit` před anonymizací (email, jméno, IČO, statistiky matchů)
  • • Auth anonymizace: email → `deleted_{id}@deleted.spravnymakler.cz`, ban 876000h, metadata vyčištěna
  • • Admin delete v `admin/users/[id]` přepsán z hard-delete na soft-delete přes Edge Function
9. března 2026
  • Fix: Validace profilu blokuje uložení notifikací
  • • Přidán řízený `activeTab` state pro `<Tabs>` komponentu
  • • Validace IČO, office address a network membership podmíněny `activeTab === "company" || "profile"`
  • • Tab Notifikace nyní ukládá bez blokování nesouvisejícími validacemi
5. března 2026
  • Fix: CRON joby failují — GUC parametr odstraněn ze Supabase
  • • CRON joby přeregistrovány — čtou klíč z `vault.decrypted_secrets` místo `current_setting()`
  • • Vault secret aktualizován na nový `sb_secret_` formát
  • • 2 zaseknuté leady (Chrást, Plzeň) auto-approved po opravě
  • • `supabase/migrations/20260305080000_fix_cron_vault_auth.sql` — nová migrace
  • • `supabase/migrations/20260124150000_setup_watchdog_cron.sql` — komentář o obsolete GUC
  • • `CLAUDE.md` — aktualizovaná sekce CRON auth
27. února 2026
  • Fix: Notifikace — deep-link s auto-expand
  • • `new_rating` → `/admin/ratings?rating_id=...` (admin vidí konkrétní hodnocení)
  • • `support_ticket` → `/admin/support?ticket_id=...` (admin vidí konkrétní ticket)
  • • `support_reply` → `/support?ticket_id=...` (seller vidí odpověď)
  • • `frontend/src/app/api/submit-rating/route.ts` — INSERT vrací ID, deep-link href
  • • `frontend/src/app/api/support-ticket/route.ts` — deep-link href s ticket_id
  • • `frontend/src/app/api/support-ticket/reply/route.ts` — deep-link href s ticket_id
  • • `frontend/src/app/admin/ratings/page.tsx` — useSearchParams + auto-expand + scrollIntoView
  • • `frontend/src/app/admin/support/page.tsx` — useSearchParams + auto-expand + scrollIntoView
  • • `frontend/src/app/support/page.tsx` — useSearchParams + auto-expand + scrollIntoView
  • Fix: Notifikace — revert "Zobrazit" tlačítka + fix rejection směrování
  • • `/notifications` stránka: revert na klik=navigace (klik na notifikaci = markAsRead + přechod na detail), odstraněno redundantní tlačítko "Zobrazit"
  • • Rejection notifikace ("vrácena k přepracování") nyní směřuje na `/seller/leads/[id]` (detail s bannerem + důvodem) místo `/seller/leads/[id]/edit` (edit stránka bez důvodu)
  • • `frontend/src/app/notifications/page.tsx` — revert klik=navigace, odebrán "Zobrazit" blok
  • • `frontend/src/app/admin/leads/[id]/page.tsx` — rejection href na detail místo edit
  • UI: Notifikace — plný text + mobile responsivita
  • • `/notifications` stránka: odstraněn `line-clamp-2` → celý body text (důvod zamítnutí apod.) je viditelný
  • • Popover zvoneček: responsive šířka (`w-[calc(100vw-2rem)] sm:w-80`) — na mobilu zabere viewport
  • • Header: badge zkrácen na číslo, "Přečíst vše" na mobilu jen ikona
  • • `frontend/src/components/notification-bell.tsx` — responsive popover šířka
  • • `frontend/src/app/notifications/page.tsx` — plný body text, responsive header
  • Fix: Chybějící emaily pro sellera (lead approval + support ticket reply)
  • • Přidány CORS headers do `send-notification-email` Edge Function (OPTIONS handler + `Access-Control-Allow-Origin/Headers` na všech response)
  • • Přidán `RESEND_API_KEY` do Coolify env vars + `.env.local`
  • • Vyměněn Resend API klíč ve všech prostředích (Supabase secrets, Coolify, lokální)
  • • Přidán response status logging do support-ticket route (`.then()` + `.catch()` místo jen `.catch()`)
  • • `supabase/functions/send-notification-email/index.ts` — CORS headers + OPTIONS handler (deploy v29)
  • • `frontend/src/app/api/support-ticket/reply/route.ts` — Resend response logging
  • • `frontend/src/app/api/support-ticket/route.ts` — Resend response logging
  • • `frontend/.env.example` — přidán `RESEND_API_KEY` placeholder
  • Fix: False positive v detekci duplicitních leadů (F19)
  • • Kontrola duplicit porovnávala pouze `parcel_number` + `unit_number`, ale ignorovala `property_type`
  • • Byt a garáž na stejné parcele jsou různé nemovitosti, ale systém je blokoval jako duplicitu
  • • Přidán `.eq("property_type", formData.propertyType)` do duplikátní kontroly
  • Fix: CRON auth — auto-approve-leads, checkin-send, watchdog-check 401 Unauthorized
  • • `auto-approve-leads` nasazena s `verify_jwt: true` (Supabase proxy odmítne CRON request před spuštěním kódu)
  • • CRON joby 4 (checkin-send) a 5 (auto-approve-leads) používaly neexistující GUC `app.settings.service_role_key` → NULL → Authorization header se neposlal
  • • `watchdog-check` neměl žádný auth check ani Authorization header v CRONu — veřejně přístupný
  • • Re-deploy `auto-approve-leads` s `verify_jwt: false` + explicitní auth check (service_role Bearer token)
  • • Re-deploy `watchdog-check` s auth checkem (předtím bez jakékoli ochrany)
  • • Oprava CRON jobů 2, 4, 5: `app.settings.service_role_key` → `supabase.service_role_key`
  • • `supabase/functions/auto-approve-leads/index.ts` — auth check přidán
  • • `supabase/functions/watchdog-check/index.ts` — auth check přidán
  • • SQL: `cron.alter_job(2)`, `cron.alter_job(4)`, `cron.alter_job(5)` (Supabase MCP)
26. února 2026
  • Fix: Admin notifikace při resubmitu leadu + Confirmation dialog
  • • Admin nedostával notifikaci když seller resubmitnul zamítnutý lead (trigger byl AFTER INSERT only)
  • • Nový UPDATE trigger `on_lead_resubmit_notify_admin` (rejected → pending_review)
  • • Funkce `notify_admin_pending_review()` rozlišuje INSERT/UPDATE přes `TG_OP` (jiný title + `is_resubmit` metadata)
  • • Confirmation dialog před resubmitem zamítnutého leadu (`LeadResubmitConfirmDialog`)
  • • Shrnutí klíčových údajů (typ, adresa, plocha, dispozice, stav, PENB, omezení, fotky)
  • • Čestné prohlášení o neexkluzivitě (povinný checkbox)
  • • Tlačítko "Zkontrolovat a odeslat" místo "Uložit změny" pro rejected leady
  • • Běžná editace pending_review leadu funguje přímo bez dialogu (bez regrese)
  • • SQL migrace: `notify_admin_on_lead_resubmit` (Supabase MCP)
  • • `components/lead-resubmit-confirm-dialog.tsx` — **NOVÝ**
  • • `seller/leads/[id]/edit/page.tsx` — dialog integrace, split handleSubmit/performUpdate
  • Brand-Consistent Email Templates (v16)
  • • Gradient top border (4px, 3 barvy: `#220860→#6039f6→#AF96FC`)
  • • Logo header (nové S-shape brand logo, 150×45px, Supabase Storage CDN)
  • • Tmavě fialový footer (`#220860` bg, `#AF96FC` nadpisy, právní údaje OneManDev s.r.o.)
  • • 3-barevný CTA gradient + solid `#6039f6` fallback pro Outlook
  • • Font stack rozšířen: Geist + system fallbacks (místo jen Arial)
  • • Zelená (`#22c55e`) → fialová (`#6039f6`) pro success stavy
  • • Ambrová (`#f59e0b`) → lavender (`#AF96FC`) pro attention stavy
  • • Oranžová (`#f97316`) → tmavě fialová (`#220860`) pro action stavy
  • • Šedý plaintext footer → dark purple branded footer
  • • 2-barevný CTA gradient → 3-barevný (konzistentní s webem)
  • • `send-notification-email/index.ts` — v16: BRAND konstanty, 4 helper funkce, redesign 11 šablon
  • • `lib/email-wrapper.ts` — **NOVÝ**: sdílený brand header/footer pro Next.js routes
  • • `api/support-ticket/route.ts` — redesign 2 inline emailů
  • • `api/support-ticket/reply/route.ts` — redesign 1 inline emailu
  • Audit & Fix: Konzistence notifikačního systému (6 oprav)
  • • Chybějící email šablony `sale_confirmed`, `sale_reported`, `rating_request` v Edge Function — emaily tiše selhávaly s 400 (→ v15, deployed)
  • • `submit-rating` posílal notifikaci sellerovi místo adminům (`p_user_id: user.id` bug) — opraveno na loop přes adminy, typ změněn na `new_rating`
  • • Duplikátní match notifikace — `create_match()` + trigger `notify_match_created()` = 2 in-app pro každého — odebrán `PERFORM create_notification()` z `create_match()` (SQL migrace)
  • • `watchdog-check` v5 — přímý Resend nahrazen Edge Function `plomba_detected`, hardcoded `ADMIN_EMAIL` → dynamický dotaz na adminy, přidána in-app notifikace
  • • `support-ticket` — přidána in-app notifikace pro adminy (`support_ticket` typ)
  • • Tiché `.catch(() => {})` v admin leads → nahrazeny `console.error()` logováním
  • • `send-notification-email/index.ts` — 3 nové šablony (v15)
  • • `watchdog-check/index.ts` — Edge Function + in-app + dynamičtí admini (v5)
  • • `api/submit-rating/route.ts` — fix admin notifikace
  • • `api/support-ticket/route.ts` — in-app pro adminy
  • • `admin/leads/[id]/page.tsx` — fix tiché .catch
  • • `lib/constants.ts` — nové notif. typy `new_rating`, `support_ticket`
  • • SQL migrace `create_match()` — odebrány duplikátní notifikace
  • Fix: Chybějící notifikace sellerovi při schválení/zamítnutí leadu
  • • Email při zamítnutí volal neexistující route `/api/send-notification` → opraveno na Edge Function `send-notification-email`
  • • In-app notifikace při zamítnutí používala neexistující parametr `p_data` → opraveno na `p_title`/`p_body`/`p_href`/`p_metadata`
  • • Seller email se nenačítal (je v `auth.users`, ne v `profiles`) → doplněn RPC `get_user_email_by_id`
  • • Dual-write notifikace sellerovi při schválení leadu (admin i auto-approve)
  • • Email šablona `lead_approved` v Edge Function v14
  • • Auto-approve CRON (v2) odesílá seller notifikace
  • • Nový typ `lead_review` v notifikačních preferencích + UI řádek "Schválení poptávky" v profilu sellera
  • • `admin/leads/[id]/page.tsx` — fetch email + prefs, fix reject, přidán approve notif.
  • • `admin/leads/[id]/lead-detail-types.ts` — `notification_preferences` v seller typu
  • • `send-notification-email/index.ts` — šablona `lead_approved` (v14)
  • • `auto-approve-leads/index.ts` — seller dual-write notifikace (v2)
  • • `lib/supabase/types.ts` — `lead_review` v `NotificationPreferences`
  • • `profile/page.tsx` — UI řádek pro `lead_review` (seller sekce)
25. února 2026
  • UI: Vizuální odlišení přečtených vs nepřečtených notifikací
  • • `notification-bell.tsx` — tečka v popoveru: barevná (nepřečtená) → šedá (přečtená)
  • • `notifications/page.tsx` — tečka na stránce notifikací: stejná logika
  • Feature: Admin Lead Detail — property-type-aware zobrazení + editace
  • • `PropertyDetailsSection` — editace přímých DB sloupců podle property_type (disposition/ownership pro byt, house_category pro dům, land_category pro pozemek atd.)
  • • `PropertyParamsSection` — editace params JSONB: rozměry, PENB, stáří/rekonstrukce, omezení (radio + checkboxy), příslušenství (9 ks byt, 8 ks dům, 7 ks komerční), vlastnosti domu (6 ks)
  • • `lead-detail-types.ts` — sdílený `LeadDetail` interface + `PROPERTY_FIELD_CONFIG` centrální mapa
  • • Odstraněn JSON dump "Další parametry" (nahrazen strukturovaným zobrazením)
  • • Přidány lookup mapy v `constants.ts`: `AMENITY_LABELS`, `HOUSE_CHARACTERISTIC_LABELS`, `HOUSE_SIZE_LABELS`
  • • Fix: amber/orange barvy v admin lead detail (`getStatusBadge`, reject button/dialog) nahrazeny brand paletou
  • UI: Design konzistence — pending_review/rejected barvy sladěny s brand paletou
  • • `LEAD_STATUS_CONFIG` a `SELLER_LEAD_STATUS_CONFIG` — pending_review: `bg-[#f5f0ff] text-[#6039f6]`, rejected: `bg-[#ede5ff] text-[#220860]`
  • • `NOTIFICATION_CONFIG` — ikony `lead_pending_review` (#6039f6) a `lead_rejected` (#220860) místo amber/orange
  • • Seller lead detail — pending/rejected bannery v on-brand barvách
  • • Admin leads — badge barvy + pending alert banner v lavender tónech
  • • Odstraněny všechny amber/orange třídy z kontextu lead statusů
  • Fix: Lead INSERT selhal kvůli trigger type mismatch
  • • DB migrace: opravena funkce `notify_admin_pending_review()` — správné volání `create_notification()` s 6 argumenty (title, body, href, metadata)
  • • Nový helper `getErrorMessage()` (`lib/utils/error.ts`) — bezpečně extrahuje `.message` z `Error`, `PostgrestError` i stringů
  • • Nahrazeno ve 12 souborech (17 výskytů `err instanceof Error ? err.message : ...`)
  • • Přidán `Sentry.captureException()` pro lead submit
24. února 2026
  • Feature: Admin Review — schvalování poptávek (F22)
  • • `pending_review` — čeká na schválení adminem (seller může editovat)
  • • `rejected` — vráceno k přepracování s důvodem (seller edituje a resubmitne)
  • • DB migrace: CHECK constraint, sloupce `review_deadline`, `reviewed_by`, `reviewed_at`, `rejection_reason`
  • • UPDATE trigger `on_lead_approved_notify_brokers` — automatická notifikace brokerů při schválení
  • • INSERT trigger `on_lead_pending_review_notify_admin` — email + in-app adminu
  • • Edge Function `auto-approve-leads` v1 (CRON každou hodinu) — auto-schválení po 24h
  • • Edge Function `send-notification-email` v21 — 2 nové typy: `lead_pending_review`, `lead_rejected`
  • • Seller: lead se vytváří jako `pending_review` s `expires_at: null`, redirect na detail
  • • Seller detail: informační bannery (pending/rejected) s důvodem a tlačítkem na editaci
  • • Seller edit: guard pro pending_review/rejected, resubmit logika (rejected → pending_review)
  • • Seller leads: nový tab "Ke schválení" (pending + rejected leady)
  • • Admin leads: amber alert banner s počtem ke schválení, filtr pending_review/rejected
  • • Admin detail: zelené tlačítko "Schválit" + oranžové "Vrátit k přepracování" (AlertDialog s důvodem)
  • • Status badge: ikony Clock (pending) a RotateCcw (rejected)
  • • Audit log: akce `approve_lead`, `reject_lead`, `resubmit_lead`
  • • Constants: LEAD_STATUS_CONFIG, SELLER_LEAD_STATUS_CONFIG, NOTIFICATION_CONFIG rozšířeny
  • • TypeScript typy regenerovány z Supabase
  • • `expires_at = NULL` při vytvoření, nastaví se na NOW+30d při schválení
  • • `reviewed_by = NULL` = auto-approved (CRON), UUID = admin akce
  • • Brokeři nevidí pending/rejected leady (RLS + view `leads_for_brokers` WHERE status='active')
23. února 2026
  • UI: Gradient border přetéká přes zaoblené rohy karet
  • • Gradient top border (`absolute top-0 h-1`) přetékal přes zaoblené rohy Card na stránkách support, contact, admin/support
  • • Oprava: `overflow-hidden` na kartách bez dropdownů, `rounded-t-2xl` workaround na kartách s Select dropdowny
  • Fix: Report-sale a checkin-respond-web API vrací 401 "Nepřihlášen"
  • • API routes `/api/report-sale` a `/api/checkin-respond-web` používaly cookie-based auth (`@/lib/supabase/server`), ale session je v localStorage → vždy 401
  • • Oprava: Bearer JWT pattern (stejně jako `submit-rating`) — frontend posílá `Authorization: Bearer <token>`, API ho ověřuje přes `@supabase/supabase-js`
  • • Dotčené soubory: `api/report-sale/route.ts`, `broker/matches/[id]/page.tsx`, `api/checkin-respond-web/route.ts`, `components/checkin-banner.tsx`
  • Feature: Rok kolaudace a rekonstrukce v parametrech leadu
  • • **Rok kolaudace** (`year_built`) — rok první kolaudace/dokončení stavby (1800–aktuální rok)
  • • **Rok poslední rekonstrukce** (`year_reconstructed`) — volitelné
  • • **Rozsah rekonstrukce** (`reconstruction_scope`) — celková/částečná, zobrazí se jen pokud je vyplněn rok rekonstrukce
  • • Data uložena v `leads.params` JSONB (žádná DB migrace)
  • • Zobrazení: wizard krok 3 + shrnutí, seller/broker/admin detail, seller edit
  • • Nezobrazuje se pro pozemky a ostatní typy
  • UI: Vyčištění Card hover efektu
  • • Odebrán globální hover efekt z Card komponenty (`hover:shadow-xl hover:border-[#AF96FC]/50 hover:-translate-y-0.5`) — aplikoval se na všechny karty včetně formulářů a tabulek
  • • Odstraněn workaround `hover:shadow-none hover:-translate-y-0` z `empty-state.tsx`
  • • Klikatelné karty (lead cards, admin stat cards) mají vlastní hover efekty
  • Legal: Cross-reference audit právních dokumentů — 28 oprav
  • • **A1**: Lhůta odvolání sjednocena na 14 dní (VOP mělo 15, RS 14)
  • • **A2**: Definice Prodávajícího ve VOP rozšířena na FO/PO (RS už mělo oboje)
  • • **A3**: Fikce doručení sjednocena na 3. pracovní den + dual-channel (VOP mělo "okamžitě")
  • • **B1**: Google OAuth přidán do Privacy Policy (tabulka zpracovatelů + účel + přenos)
  • • **B2**: Mapy.cz/Seznam.cz přidán do Privacy Policy
  • • Mediační krok před soudem (čl. 19.2), Force Majeure (čl. 16.4)
  • • Lhůta kontaktování po Matchi 5 prac. dní (čl. 7.1.a), SLA odvolání 30 dní (čl. 12.2.c)
  • • Cross-property provize zúžena (čl. 6.5), přechodové pravidlo pro bidy (čl. 17.3)
  • • Cenový semafor — "vysoká/běžná/nízká cena" místo technických kódů (čl. 2.8, 10.1)
  • • Právo makléře na odpověď na hodnocení (čl. 11.4), progresivní sankce 150 % (čl. 14.5)
  • • Příklady pro okamžitou suspenzi (čl. 12.3), postoupení práv Provozovatele (čl. 19.8)
  • • Sjednocení lhůty, definic, fikce doručení (A1-A3), aktivní Match upřesněn (A4)
  • • Právo makléře na odpověď na hodnocení (čl. 9.7)
  • • 3 noví zpracovatelé (Google, Mapy.cz, support tickets) v sekcích 3.15-3.17 + tabulce 4.1
  • • Balancing testy u 6 oprávněných zájmů (3.3, 3.7, 3.9, 3.10, 3.13, 3.14)
  • • DPIA zmínka u Watchdog (3.7), souhrnná tabulka doby uchování (8.5)
  • • Notifikační preference pro sellery (2.1)
  • Legal: Komplexní revize právních dokumentů v2.0 (~40 změn)
  • • Nové definice: 2.13 Realizovaný obchod (vč. nepřímých převodů přes s.r.o.), 2.14 Skutečná prodejní cena (vč. vedlejších plnění), 2.15 Propojená osoba (5 kategorií)
  • • Registrace: souhlas→akceptace (GDPR konzistence), Propojené osoby v multi-account detekci
  • • Provize: 5.3 formy odměny (sníží/promine/jiná forma), 5.9 sporná prodejní cena, 5.10 námitka fakturace
  • • Vznik nároku: 6.5 provize z dalších nemovitostí (24 měsíců), 6.6 notifikace plomby + 7denní lhůta, 6.7 nepřímý převod = obchod, 6.8 lhůta 30 dní na fakturu
  • • Anti-bypass: 8.1.g zákaz identifikace adresy z anonymizovaných údajů, 8.2 temporal regime pro g), 8.5 trojnásobek→pětinásobek s odůvodněním, 8.6 Propojená osoba dle 2.15
  • • Monitoring: 9.2 automatické +12 měsíců při aktivní spolupráci, 9.4 7denní lhůta + neodpovězení = porušení
  • • Sankce: 2 nové řádky (neodpovězení check-in 10k, nadhodnocení 25k), obcházení 3x→5x
  • • Změny smlouvy: 17.4 ochrana aktivních matchů
  • • Závěrečná: 19.5 doručovací fikce 3. pracovní den + dual-channel, 19.8 promlčecí lhůta 10 let
  • • Nové definice: Úspěšný prodej, Aktivní Match, Rezervní nabídka, Opakované porušení
  • • Prodávající: odpovědnost za škodu, plná moc, 7denní check-in, re-posting, pre-existing relationships
  • • Makléři: zákaz využití pro kupující, self-dealing prohibition
  • • Match: 14denní fallback pro neúplné sloty, 7denní výběr, match cancellation při neodpovědi 14 dní
  • • Hodnocení: i po cancelled spolupráci, právo makléře na odpověď
  • • Odpovědnost: oddělené capy (broker provize-based, seller 50k CZK)
  • • Ukončení: 14 dní (ne 15), monitoring přežívá smazání
  • • Doručení: 3. pracovní den + dual-channel
  • • Data: IP adresa + user-agent, support tickety, retenční politika fotografií
  • • Účely: 3 nové (support tickets, Mapy.cz geokódování, Google OAuth)
  • • Balancing testy pro 4 oprávněné zájmy
  • • Zpracovatelé: oprava Supabase sídla (USA, data EU Frankfurt), přidáni Seznam.cz/Mapy.cz a Google
  • • Přeshraniční přenosy: Supabase a Google přidáni (SCCs + DPF)
  • • Práva: čl. 22 GDPR (automatizované rozhodování)
  • • Smazání účtu: rozšířená retenční politika + deleted_profiles popis
  • • Cookies: localStorage zmínka
  • • `LEGAL-TERMS-BROKER.md`, `LEGAL-TERMS.md`, `LEGAL-PRIVACY.md`
  • • `frontend/src/app/terms/broker/page.tsx`
  • • `PRAVNIK-BRIEF.md`, `ROADMAP.md`
19. února 2026
  • Fix: Shrnutí nového leadu — chybějící adresa a fotografie
  • • Adresa se nyní zobrazuje kompletní (ulice, město, PSČ), oblast zvlášť pokud se liší
  • • Přidány náhledy nahraných fotek (64×64px thumbnaily) s celkovým počtem
  • • Automaticky získaná data (parcela, LV, k.ú.) se záměrně nezobrazují — seller je nepotřebuje
  • • `app/seller/new-lead/step-5-summary.tsx` — plná adresa, foto náhledy
  • Fix: Prodloužení leadu — výpočet expirace + UX omezení
  • • `app/seller/leads/[id]/page.tsx` — oprava výpočtu, disabled logika, toast
  • • `app/api/extend-lead/route.ts` — konzistentní oprava výpočtu
  • Fix: Prodloužení leadu — auth mismatch (401 "Nepřihlášen")
  • • `app/seller/leads/[id]/page.tsx` — client-side extend s validací, RLS, audit log, analytics
  • Feat: Responsive Navbar — hamburger menu + user dropdown
  • • `components/ui/sheet.tsx` — shadcn Sheet (slide-out panel)
  • • `components/ui/dropdown-menu.tsx` — shadcn DropdownMenu
  • • `components/user-menu.tsx` — desktop dropdown (Profil, Podpora, Odhlásit)
  • • `components/mobile-nav.tsx` — mobilní hamburger Sheet
  • • `components/navbar.tsx` — responsivní layout s `md:` breakpointem, Bell vždy viditelný, CTA Registrovat vždy viditelná
  • • Desktop (md+) přihlášený: Logo | Dashboard | Podpora | Bell | [Jméno ▼]
  • • Mobil přihlášený: Logo | Bell | [☰] → Sheet
  • • Desktop nepřihlášený: Logo | Kontakt | Přihlásit | Registrovat
  • • Mobil nepřihlášený: Logo | Registrovat | [☰] → Sheet
  • Feat: Support Ticket System
  • • `app/api/support-ticket/route.ts` — vytvoření ticketu (anon + auth), rate limit 3/5min, emaily, audit log
  • • `app/api/support-ticket/reply/route.ts` — admin odpověď s emailem + in-app notifikací
  • • `app/contact/page.tsx` — veřejný kontaktní formulář s pre-fill pro přihlášené
  • • `app/support/page.tsx` — historie vlastních ticketů (auth required)
  • • `app/admin/support/page.tsx` — admin správa: filtry, sorting, CSV export, inline reply
  • • `lib/rate-limit.ts` — nový limit `supportTicket: 3/5min`
  • • `admin/layout.tsx` — "Podpora" v navigaci
  • • `admin/audit/page.tsx` — nové ACTION_LABELS + ENTITY_LABEL pro support tickety
  • • `components/navbar.tsx` — odkaz "Kontakt" pro nepřihlášené, "Podpora" pro přihlášené
  • • `app/page.tsx` — "Kontaktní formulář" ve footer sekci Kontakt
  • • Tabulka `support_tickets` s RLS, indexy, auto-update trigger
  • Fix: Konzistentní validace adresy ve wizardu
  • • `components/address-autocomplete.tsx` — nový `onManualChange` callback (reset odvozených dat při manuální editaci textu)
  • • `seller/new-lead/step-2-location.tsx` — pole Město/PSČ jsou readOnly (plní se z autocomplete), tlačítko "Pokračovat" vyžaduje souřadnice (lat/lng), varování při nezvolené adrese
  • Fix: Konzistentní validace plochy (size_m2) mezi wizard a editací
  • • `lib/utils/validation.ts` — `validateNumericInput()` (clamp v onChange), `isInRange()` (submit safety net)
  • • `lib/constants.ts` — `SIZE_LIMITS` konstanty + `getSizeLimits()` helper
  • • `seller/new-lead/step-3-parameters.tsx` — onChange validace na sizeM2/landAreaM2, dynamický max, disabled button kontrola
  • • `seller/new-lead/page.tsx` — submit safety net před insertem
  • • `seller/leads/[id]/edit/page.tsx` — onChange validace na všech sizeM2 inputech + landAreaM2, submit safety net
  • Feat: Google OAuth pro sellery (F17)
  • • `components/google-sign-in-button.tsx` — GoogleSignInButton komponenta (oficiální Google "G" ikona, loading/error state)
  • • `app/auth/callback/route.ts` — smart redirect (admin→/admin, neověřený→/auth/verify, ověřený→/dashboard)
  • • `app/auth/verify/page.tsx` — formulář pro zadání telefonu (Google OAuth seller nemá phone), dynamický popis dle stavu email verifikace
  • • `app/auth/login/page.tsx` — divider "nebo" + GoogleSignInButton pod formulářem
  • • `app/auth/register/seller/page.tsx` — GoogleSignInButton navrchu + divider "nebo vyplňte formulář"
  • • `handle_new_user()` — detekce Google providera (`raw_app_meta_data->>'provider'`), automaticky `role=seller`, `email_verified_at=NOW()`, parsování `full_name` na `first_name`/`last_name`
  • • Google provider zapnut (Client ID + Secret)
  • • Redirect URLs nastaveny (wildcard `/**`)
  • • Automatic Linking (výchozí chování Supabase)
  • • OAuth 2.0 Client (Web application) — 3 JS origins, 1 redirect URI
  • • App publikována (In production)
18. února 2026
  • Feat: OG image pro sociální sítě (F15 dokončeno)
  • Fix: Landing page — mobilní responsivita obrázků
  • • **Hero** — přidán druhý `<Image>` s `lg:hidden` pod text (desktop zachován absolutní pozicí)
  • • **WhySellersSection** — `order-last lg:order-first` místo `hidden lg:block`
  • • **HowItWorksSection** — odstraněn `hidden lg:block`, responzivní výšky
  • SEO: F5 dokončení — robots.txt, sitemap.xml, JSON-LD, Image optimalizace
  • • `public/robots.txt` — pravidla pro crawlery (Disallow: /admin, /api, /dashboard, /auth, /seller, /broker, /notifications, /checkin)
  • • `app/sitemap.ts` — statický sitemap (/, /privacy, /terms, /terms/broker, /changelog)
  • • Organization schema (OneManDev s.r.o., adresa, logo, email)
  • • WebSite schema (název, URL, jazyk)
  • • FAQPage schema (6 otázek z FAQ sekce)
  • • Nahrazeny raw `<img>` za Next.js `<Image>` ve 4 souborech (page.tsx, why-sellers-section, how-it-works-section, compare-section)
  • • Hero building: `priority={true}` pro LCP optimalizaci
  • • Automatický WebP + srcset pro všechny obrázky
  • • hero-building.png: "Moderní bytový dům s balkóny — prodej nemovitosti"
  • • compare-landscape.png: "Realitní makléři konzultují prodej nemovitosti"
  • • how-it-works.png: "Podání rukou při dohodě o prodeji nemovitosti"
  • • compare-house.png: "Rodinný dům — srovnání nabídek makléřů"
  • Security: Hardening — CRON auth, Next.js CVE, race conditions, IP spoofing, headers
  • • 3 CRON Edge Functions (`watchdog-check` v10, `expire-leads` v4, `checkin-send` v4) — přidán service_role_key auth check, deploy do produkce
  • • Next.js 16.0.7 → 16.1.6 — oprava known CVEs (DoS, source exposure)
  • • `checkin-respond` — atomic UPDATE (race condition) + `Referrer-Policy: no-referrer` (token leak)
  • • `verify-email` — rate limit 10/min + atomic UPDATE (race condition)
  • • `getClientIp()` helper — anti-spoofing IP extraction (x-real-ip preference, poslední IP z x-forwarded-for)
  • • 11 API routes aktualizováno na `getClientIp()`
  • • Security headers v `next.config.ts` — HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
  • • Smazán legacy `backend/` adresář (nepoužívaný Python prototyp)
  • • `next.config.ts` — security headers + turbopack.root
  • • `lib/rate-limit.ts` — `getClientIp()`, `verifyEmail` preset
  • • `auth/verify-email/route.ts` — atomic update + rate limit
  • • `api/checkin-respond/route.ts` — atomic update + safeRedirect
  • • 10 dalších API routes — `getClientIp()` migration
  • • 6 test souborů — mock updates pro `getClientIp`
  • Content: Landing page SEO + faktická správnost
  • • `app/layout.tsx` — meta title, description, OG/Twitter tagy (SEO keywords "prodej nemovitosti")
  • • `app/page.tsx` — H1 s SEO keywords, hero popis, trust badges, copyright 2025→2026
  • • `components/landing/why-sellers-section.tsx` — H2, podtitulek, "zdarma" místo "platíte při úspěchu"
  • • `components/landing/how-it-works-section.tsx` — H2 se SEO keywords, kroky bez "500m" a "z okolí"
  • • `components/landing/compare-section.tsx` — H2, features (jen % provize, semafor popis)
  • • `components/landing/for-brokers-section.tsx` — provize specifikována (20 %)
  • • `components/landing/testimonial-section.tsx` — fiktivní testimonial nahrazen value proposition kartami
  • • `components/landing/faq-section.tsx` — semafor opraven, min. provize 5 000 Kč, poloha nemovitosti
  • • `components/hero-buttons.tsx` — CTA text, link na /auth/register/seller
  • • Cenový semafor: zelený/žlutý/červený → štítky Vysoká/Nízká cena
  • • "% vs. fixní" provize → jen procentuální
  • • "Platíte až při úspěchu" → prodávající neplatí vůbec
  • • "Makléři z okolí/regionu" → odstraněno (makléř se může přihlásit odkudkoli)
  • • "Až 5 nabídek" → "5 nabídek" (odemknou se při 5)
  • • Fiktivní testimonial "Jan N." → 3 karty (5 nabídek, 0 Kč, ověření makléři)
  • • Copyright 2025 → 2026
  • • Minimální provize 5 000 Kč bez DPH doplněna do FAQ
16. února 2026
  • Fix: Match inline select — validace state machine
  • • `app/admin/matches/page.tsx` — přidána `VALID_TRANSITIONS` mapa, validace v `handleStatusChange`, filtr v `<SelectContent>`
  • • Nová konstanta `VALID_TRANSITIONS` definuje povolené přechody pro každý stav
  • • `handleStatusChange` validuje přechod před UPDATE — nevalidní odmítne s toast chybou
  • • `<SelectContent>` zobrazuje jen aktuální stav + validní cíle (finální stavy jsou readonly)
  • Testing Matrix — Kompletní testovací matice
  • • `TESTING-MATRIX.md` — kompletní testovací matice v markdown (16 sekcí, 471 test casů)
  • • `TESTING-MATRIX.csv` — CSV export pro Excel/Google Sheets (UTF-8 BOM, středníky)
  • F1: Právní dokumenty v2.0
  • • `app/terms/page.tsx` — Podmínky služby v2.0 (16 sekcí, definice, pravidla, match, monitoring, hodnocení, suspenze, spory)
  • • `app/terms/broker/page.tsx` — Rámcová smlouva pro makléře v2.0 (19 sekcí, provize 20%, anti-bypass, sankce, watchdog, check-in)
  • • `app/privacy/page.tsx` — Zásady ochrany osobních údajů v2.0 (13 sekcí, 14 účelů zpracování, GDPR práva, zpracovatelé, třetí země)
  • • `LEGAL-TERMS.md` — zdrojový markdown Podmínek služby
  • • `LEGAL-TERMS-BROKER.md` — zdrojový markdown Rámcové smlouvy
  • • `LEGAL-PRIVACY.md` — zdrojový markdown Zásad ochrany osobních údajů
  • • `PRAVNIK-BRIEF.md` — aktualizovaný podklad pro právníka (9 sekcí, 33 otázek)
  • • Odstraněn warning banner „Toto je pracovní verze smlouvy" z broker terms
  • • Cross-linky mezi dokumenty (VOP → Rámcová smlouva → Privacy)
  • • Tabulka sankcí (obcházení 100k Kč / 3x provize, nepravdivý check-in 50k Kč)
  • • Tabulka zpracovatelů (Supabase IE, Hetzner DE, Resend US, Vonage NL, Sentry US)
  • • SCCs/DPF pro přenosy do USA (Resend, Sentry)
  • • ČOI + ODR odkaz pro spotřebitele, ÚOOÚ kontakt
  • F18: Admin 2FA (TOTP)
  • • `hooks/useMfa.ts` — centralizovaný MFA hook (enroll, verify, unenroll, AAL check)
  • • `app/auth/mfa-verify/page.tsx` — verifikační stránka po loginu (6-ciferný OTP, auto-submit)
  • • `app/auth/mfa-setup/page.tsx` — 3-krokový setup wizard (instrukce → QR kód → ověření)
  • • `components/mfa-setup-section.tsx` — 2FA správa v profilu (setup/unenroll dialogy)
  • • `hooks/useAuthGuard.ts` — nová `requireMfa` option, async KONTROLA 3.5 (AAL level + listFactors)
  • • `app/admin/layout.tsx` — zapnuto `requireMfa: true`
  • • `app/auth/login/page.tsx` — admin redirect na `/admin` (useAuthGuard zachytí MFA)
  • • `app/profile/page.tsx` — MfaSetupSection v tabu Účet (pouze admin)
  • • `app/admin/audit/page.tsx` — 4 nové audit labels (mfa_enroll/unenroll/verify_success/verify_failed)
  • • `hooks/index.ts` — export useMfa
  • Refaktoring: Dead code cleanup
  • • `leads.extra_slots_unlocked` — nikdy implementovaný mechanismus 5+2 slotů
  • • `leads.floor_plan_url` — nikdy implementovaný upload půdorysu
  • • `bids.proof_description` — nikdy implementovaný popis důkazu
  • • `matches.invoice_generated` — odložená Fakturoid integrace
  • • `seller_leads_summary` VIEW — nikde nedotazovaný
  • • `hooks/useFetchData.ts` — nikdy importovaný hook
  • • `app/api/notifications/route.ts` — nepoužívaná API route (frontend dotazuje Supabase přímo)
  • • `components/ui/form.tsx` — nepoužívaný shadcn Form wrapper
  • • `proxy.ts` — staging proxy bez middleware
  • • 9 mrtvých exportů z constants.ts (ENERGY_RATING_LABELS, ENCUMBRANCE_VALUES, NETWORK_LABELS aj.)
  • • 5 mrtvých type aliasů z types.ts (Match, CheckinToken, BrokerRating aj.)
  • • `validateParams()` z api.ts, `notifications` rate limit
  • • 6 console.log v useAuthGuard.ts nahrazeno komentáři
  • • Autogenerované TypeScript typy synchronizovány se Supabase
13. února 2026
  • Refaktoring: New-lead wizard split
  • • `types.ts` — FormData, StepProps, INITIAL_FORM_DATA
  • • `step-1-property-type.tsx` — výběr typu nemovitosti
  • • `step-2-location.tsx` — adresa / mapa + katastr
  • • `step-3-parameters.tsx` — parametry per typ nemovitosti
  • • `step-4-photos.tsx` — upload fotek interiér/exteriér
  • • `step-5-summary.tsx` — shrnutí + čestné prohlášení
  • Feat: F16 — Export CSV z admin tabulek
  • • `lib/csv-export.ts` — utilita: BOM pro Excel, středník jako oddělovač, escapování
  • • `components/csv-export-button.tsx` — reusable tlačítko s Download ikonou
  • • `__tests__/utils/csv-export.test.ts` — 16 testů (escapování, BOM, formátování, edge cases)
  • Feat: F13 — Hodnocení makléřů
  • • `/seller/rate/[matchId]` — formulář s 4 sekcemi (povinné hvězdy + doporučení, volitelné kategorie, kontextové otázky z nabídky, komentář max 500 znaků)
  • • `/admin/ratings` — tabulka s moderací (schválit/zamítnout), filtry dle statusu, rozbalitelné detaily
  • • `star-rating.tsx` — interaktivní star picker (1-5 hvězd, brand barvy)
  • • CTA banner v seller lead detailu "Ohodnoťte svého makléře"
  • • DB tabulka `broker_ratings` s RLS (seller insert/select, admin select/update, broker select approved)
  • • API `/api/submit-rating` s rate limitingem, validací, audit logem
  • • Edge Function `send-notification-email` v20 — nový typ `rating_request`
  • • Trigger v `handleConfirmSale()` — email + in-app notifikace sellerovi
  • Feat: F19 — Detekce duplicitních leadů
  • • Stejná parcela + stejná (nebo obě prázdná) jednotka → blokace s odkazem na existující poptávku
  • • Stejná parcela + různá jednotka → projde (jiná nemovitost)
  • • Bez parcelního čísla → kontrola se přeskakuje
  • • Kontrola v `handleSubmit()` přímým Supabase dotazem (RLS, žádný nový API endpoint)
  • • UI blok s informací a tlačítkem na existující poptávku
  • • DB partial index `idx_leads_duplicate_check` pro rychlé vyhledávání
  • Feat: F11 — Pokročilé filtry leadů pro makléře
  • • Typ nemovitosti — badge toggle buttons (Vše + 5 typů, klik na vybraný zruší)
  • • Subkategorie — kontextový Select (byt→dispozice, dům→kategorie, pozemek→kategorie...)
  • • Stav nemovitosti — Select (novostavba, po rekonstrukci, před rekonstrukcí...)
  • • Plocha m² — dva Input pole (od/do) s debounce 300ms
  • • Řazení — Select (nejnovější, brzy vyprší, málo slotů, největší plocha)
  • • Export `getLeadSubcategory` z `lead-card.tsx` (bylo privátní)
  • • Fix `parseLeadParams` — přidáno parsování `rooms`
  • • Tab počty se počítají z `advancedFilteredLeads` (ne z `locationFilteredLeads`)
  • • "Zrušit filtry" resetuje všechny filtry včetně nových
  • • Counter "X z Y" reflektuje aktuální filtrovaný+seřazený stav
12. února 2026
  • Fix: In-app notifikace se nezobrazovaly
  • • `hooks/useNotifications.ts` — fetch() nahrazen primymi Supabase dotazy, hook prijima `supabase` klienta jako parametr
  • • `components/notification-bell.tsx` — pridan import `useAuth`, predava `supabase` do hooku
  • • `app/notifications/page.tsx` — vsechny fetch() nahrazeny primymi Supabase dotazy pres `useAuthGuard().supabase`
  • Feat: F9 — Audit Log
  • • Nova tabulka `audit_logs` (UUID PK, user_id, user_role, action, entity_type, entity_id, changes JSONB, metadata JSONB)
  • • RLS: admin-only cteni, authenticated INSERT (user_id = auth.uid() nebo NULL)
  • • 4 indexy: created_at DESC, action, (entity_type, entity_id), user_id
  • • `lib/audit.ts` — `logAuditAction()` fire-and-forget (nikdy neblokuje UX)
  • • Admin: suspend_user, unsuspend_user, change_role, delete_user, confirm_sale, issue_invoice, record_payment, cancel_match, revert_to_watching, approve_sale_report, reject_sale_report, change_match_status
  • • Broker: report_sale, submit_bid, checkin_respond
  • • Seller: create_lead, create_match, extend_lead
  • • `/admin/audit` — filtry (akce, entita, uzivatel, datum od-do), razeni, paginace
  • • Rozbalitelne radky s detailem zmen a metadat
  • • Badge barvy podle role (admin fialova, broker modra, seller seda, system seda)
  • • Navigacni odkaz v admin sidebar (ScrollText ikona)
  • Feat: F15 (částečně) + F5 (OG tagy) — Umami Analytics + Open Graph
  • • **Cookieless tracking** — GDPR bez consent banneru, žádné cookies
  • • **Podmíněné načtení** — `<Script>` se renderuje pouze pokud jsou nastaveny `NEXT_PUBLIC_UMAMI_URL` + `NEXT_PUBLIC_UMAMI_WEBSITE_ID`
  • • **5 custom events** (konverzní body):
  • • **`trackEvent()` utilita** (`lib/utils/analytics.ts`) — production-only, v dev loguje do konzole, fire-and-forget
  • • **UTM parametry** — Umami trackuje automaticky (utm_source, utm_medium, utm_campaign)
  • • `metadataBase`, `openGraph`, `twitter` metadata v layout.tsx
  • • Title: "Správný Makléř – Nechte makléře soutěžit o vaši nemovitost"
  • • Description: "Zadejte nemovitost zdarma a získejte až 5 nabídek od ověřených makléřů."
  • • OG image placeholder: `og-image.png` (1200×630, zatím nutno vytvořit manuálně)
  • UX: Sloučení notifikačních karet v profilu makléře
  • • **Collapsible panel** — řádek "Nové poptávky v oblasti" je klikatelný, rozbalí výběr krajů přímo pod sebou
  • • **RadioGroup** — přepínač "Všechny kraje" / "Vybrané kraje" s grid checkboxů 14 krajů
  • • **Dynamický popisek** — pod-text řádku zobrazuje aktuální stav ("Všechny kraje" nebo "Vybrané kraje (N)")
  • • **Disabled stav** — panel krajů se deaktivuje pokud jsou oba kanály (email + in-app) vypnuté
  • • **Beze změny backendu** — `preferred_regions` se ukládá stejně jako dosud
11. února 2026
  • Feat: Granulární notifikační preference (JSONB matice)
  • • **6 typů notifikací**: Nové poptávky, Match, Potvrzení prodeje, Check-in, Nabídky připraveny, Expirace poptávky
  • • **2 kanály**: Email + In-app — každý typ lze nezávisle zapnout/vypnout
  • • **Opt-out model** — výchozí stav je "vše zapnuto", uživatel vypíná co nechce
  • • **Zamčené in-app** — operační notifikace (match, check-in) mají in-app vždy zapnutý
  • • **Backend respektuje preference**: 3 SQL trigger funkce, Edge Functions `expire-leads` v3 + `checkin-send` v3, admin match detail
  • • **Zpětná kompatibilita** — staré sloupce se stále zapisují paralelně
  • • **Admin panel** — zobrazení JSONB preferencí v detailu uživatele
  • Fix: Staging password loop po přihlášení
  • • **Nahrazení HTTP Basic Auth za cookie-based ochranu** — `proxy.ts` nyní kontroluje HttpOnly cookie `staging_access` (SHA-256 hash hesla)
  • • **Nová stránka `/auth/staging`** — formulář na zadání staging hesla s brand designem
  • • **Nový API endpoint `/api/staging-auth`** — ověří heslo, nastaví cookie (30 dní, HttpOnly, SameSite=Lax)
  • • Cookie se posílá automaticky na všechny requesty (fetch, SSR, navigace) → žádná smyčka
  • Feat: F12 — In-app notifikace (zvoneček + dual-write)
  • • **DB**: Tabulka `notifications` s RLS (select_own, update_own, insert_authenticated)
  • • **SQL funkce `create_notification()`** (SECURITY DEFINER) — volatelná z Edge Functions i API routes
  • • **Hook `useNotifications`** — polling 60s, mark as read, mark all as read
  • • **Komponenta `NotificationBell`** — zvoneček v navbaru s badge nepřečtených, popover dropdown s posledními 10 notifikacemi
  • • **API `/api/notifications`** — GET (paginated, filtr unread_only) + PATCH (mark read: single ID nebo all)
  • • **Stránka `/notifications`** — kompletní seznam, seskupení po dnech (Dnes/Včera/Starší), filtr nepřečtené
  • • **Dual-write retrofit**: `send-notification-email` v16 — po odeslání emailu i `create_notification()` RPC
  • • **Dual-write**: report-sale (admin notifikace), watchdog-check (admin alert), expire-leads (seller upozornění)
  • • **Integrace do navbaru** — NotificationBell mezi Dashboard linkem a jménem uživatele
  • Feat: F10 — Match check-in systém (3-měsíční dotazník obou stran)
  • • **DB**: Tabulka `checkin_tokens` (tokenizované 1-click odpovědi) + sloupce v matches (last_checkin_at, next_checkin_at, checkin_round, checkin_status)
  • • **Edge Function `checkin-send`** v1 (CRON denně 9:00 UTC) — query matches kde `next_checkin_at <= NOW()`, email + in-app notifikace obou stran
  • • **Email šablony**: `checkin_broker`, `checkin_seller`, `checkin_reminder` — 3 CTA tlačítka (Probíhá / Prodej dokončen / Problém)
  • • **API `/api/checkin-respond`** — token-based 1-click odpověď z emailu (bez přihlášení, GET s redirect)
  • • **API `/api/checkin-respond-web`** — auth-based odpověď z webové aplikace (POST s JWT)
  • • **Vyhodnocovací logika**: obě "ongoing" → ok; obě "completed" → nahlásit prodej; nesoulad → red_flag + admin notifikace
  • • **Upomínka**: 14 dní bez odpovědi → reminder email; 2+ neodpovědí → red flag
  • • **Komponenta `CheckinBanner`** — brand card s gradient border, 3 tlačítka, success stav
  • • **Integrace**: broker match detail (`/broker/matches/[id]`) + seller lead detail (`/seller/leads/[id]`)
  • • **Stránka `/checkin/success`** — potvrzení po odpovědi z emailu + "strašák" monitoring katastru
  • • **Admin UI**: check-in sloupec v tabulce matchů (ok/pending/red_flag badge), red flag filtr, check-in historie v match detailu (tabulka odpovědí)
  • Feat: F4 — Sentry integrace (error tracking)
  • • **Client**: Zachytává JS errory v prohlížeči, Session Replay (100% sessions s errorem)
  • • **Server**: API routes, Server Components, server actions
  • • **Edge**: Middleware errory
  • • `global-error.tsx` — React error boundary pro App Router
  • • `instrumentation.ts` — registrace Sentry pro Node.js a Edge runtime
  • • `next.config.ts` — `withSentryConfig` wrapper, auto-instrumentace
  • • Build přepnut na webpack (`next build --webpack`) — Turbopack má bug s `node:inspector` v názvu chunku na Windows
  • • Sentry posílá data pouze v produkci (`enabled: process.env.NODE_ENV === "production"`)
  • Fix: F3 — Account Deletion Cleanup (kritický bug fix)
  • • **KRITICKÝ BUG FIX**: `auth.admin.deleteUser()` spouštělo CASCADE chain (auth.users → profiles → leads/bids/matches), čímž mazalo veškerá data včetně matchů a faktur
  • • Nahrazeno za **ban + anonymizaci**: `updateUserById()` s ban_duration ~100 let + anonymizace emailu
  • • **Broker**: Nelze smazat s aktivními matchi (watching/sold/invoiced) — vrací ACTIVE_MATCHES error s počtem a detaily
  • • **Seller**: Automatické zrušení aktivních leadů (→ cancelled) + odmítnutí pending bidů (→ rejected)
  • • **Rozšířená anonymizace**: nově i first_name, last_name, bank_account, office_*, network_*, verifikační pole, terms_*
  • • Smazání broker_service_profiles při mazání brokera
  • • DB migrace: rozšíření `account_deletion_audit` o first_name, last_name, bank_account, leads_cancelled, bids_rejected
  • • Role-specific popis v delete dialogu (broker vs seller)
  • • Upozornění pro brokery o blokaci při aktivním spárování
  • • Parsování ACTIVE_MATCHES chyby z Edge Function s detailní zprávou
  • Feat: F6 — Potvrzovací dialog před odesláním nabídky
  • • Validace formuláře (suspended check, služby) → otevření dialogu místo přímého odeslání
  • • Rekapitulace: odhadovaná cena, provize (%), služby (badge tagy), další služby, zpráva (zkrácená na 100 znaků)
  • • Tlačítka "Zpět k úpravám" a "Odeslat nabídku"
  • • Doplňuje F2 (race condition ochrana) — poslední šance zkontrolovat údaje před INSERT
  • Feat: F2 — Race condition ochrana nabídek (BEFORE INSERT trigger)
  • • **`check_bid_limits()`** — kontrola `max_bids` limitu a stavu leadu před vložením nabídky
  • • **`FOR UPDATE` lock** na řádku leadu → atomická kontrola (serializuje souběžné INSERTy)
  • • Kontrola stavu leadu (musí být `active`) — custom ERRCODE `P0001`
  • • Počítání pouze ne-rejected bidů proti limitu — custom ERRCODE `P0002`
  • • `SECURITY DEFINER` + `SET search_path = public`
  • • Rozlišení chybových hlášek: neaktivní lead, plný lead, duplicitní nabídka
  • • Re-fetch lead dat po chybě (aktualizace UI při změně stavu)
  • • Kontrola slotů: pokud `bid_count >= max_bids` → "Všechna místa obsazena" místo formuláře
9. února 2026
  • Remove: Testing banner odebrán z layoutu
  • Feat: Floating navbar + windowed landing page layout
  • • Zaoblené rohy (`rounded-2xl`), brand purple shadow, border
  • • Responsivní margins (`mx-4 sm:mx-6 lg:mx-8`), max šířka `max-w-7xl`
  • • Sticky s odsazením od vrchu (`top-3 mt-3`)
  • • Backdrop blur efekt zachován
  • • Všechny sekce mají shodné margins a max-width jako navbar
  • • Barevné sekce (Hero, HowItWorks, ForBrokers, Testimonial, CTA, Footer) se zaoblenými rohy
  • • Footer s větším vnitřním paddingem pro lepší čitelnost
  • Fix: Chybějící soubory v git repo (Coolify build fail)
  • Fix: Dropdown adresního našeptávání oříznutý v profilu
  • Feat: Role přesunuta z pruhu do navbaru
  • • Jméno uživatele + role s barevnou tečkou
  • • Na mobile jen křestní jméno
  • • Klik vede na profil
  • • Smazána komponenta `role-indicator.tsx`
  • Feat: České skloňování křestních jmen (5. pád / vokativ)
  • • Heuristická pravidla pro 5. pád českých křestních jmen (bez externích závislostí)
  • • Pokrytá pravidla: -a→-o (Jana→Jano), -ek→-ku (Marek→Marku), -ch→+u (Vojtěch→Vojtěchu), sykavky +i (Tomáš→Tomáši), -k→-ku (Patrik→Patriku), tvrdé souhlásky +e (Michal→Michale)
  • • Nepravidelné tvary: Petr→Petře, Pavel→Pavle, Karel→Karle, Zdeněk→Zdeňku
  • • Neměnné koncovky: -ie (Marie), -í (Jiří), -o (Hugo), -e (René)
  • • Cizí a neznámá jména zůstávají beze změny (fallback)
  • • Zachování velikosti písmen (PETR→PETŘE, petr→petře)
  • • 46 nových testů (mužská, ženská, cizí jména, edge cases)
  • • Celkem: **164 testů v 14 suites**
  • Feat: Brand design refresh — kompletní migrace barev na brand paletu
  • • **28+ stránek** — admin, auth, broker, seller, profil, dashboard, landing
  • • **4 komponenty** — button, card, bid-comparison-table, lead-card
  • • **1 config** — constants.ts (status badges, PENB barvy)
  • • **Button**: nový `lavender` variant (`bg-[#AF96FC]`) pro auth a sekundární akce
  • • **Card**: `rounded-2xl`, `shadow-md shadow-[#6039f6]/5`, lepší hover efekty
  • • **Auth layout wrapper** (`auth-layout.tsx`) — reusable gradient pozadí pro 6 auth stránek
  • • **Seller wizard stepper** — gradient kroužky, checkmark ikony, scale animace, smooth progress bar
  • • **Layout** — `min-h-[calc(100vh-4rem)]` fix pro správné pozicování footeru
  • • Active/Success: `bg-[#e0d5f5] text-[#220860]` (místo green)
  • • Error messages: `bg-[#f5f0ff] border-[#220860]/10 text-[#220860]` (místo red)
  • • Warning/Info: `bg-[#f5f0ff] border-[#e0d5f5]` (místo amber/yellow)
  • • PENB A-B: `#6039f6`, C: `#AF96FC`, D-G: `#220860` s klesající opacitou
6. února 2026
  • Feat: Provizní a fakturační systém
  • • `invoice_number` — číslo faktury (reference na externí systém)
  • • `invoice_issued_at` — datum vystavení faktury
  • • `invoice_due_date` — datum splatnosti
  • • `invoice_paid_at` — datum zaplacení
  • • `actual_commission_percent` — skutečná provize makléře (pokud se liší)
  • • `sale_confirmed_at` — datum potvrzení prodeje adminem
  • • `admin_notes` — poznámky admina
  • • `broker_response_deadline` — deadline pro reakci makléře
  • • **Časová osa** — 5 kroků: spárováno → prodáno → potvrzeno → fakturováno → zaplaceno
  • • **Info sekce** — nemovitost, kontakty stran (vč. emailů), nabídka makléře
  • • **Prodej** — detekce plomby, potvrzení prodeje, editace prodejní ceny
  • • **Fakturace** — live výpočet provize (20 % z nabídkové, min. 5 000 Kč + DPH 21 %)
  • • **Akční tlačítka** — kontextová dle stavu (Potvrdit prodej, Vystavit fakturu, Zaznamenat platbu, Zrušit)
  • • **Poznámky** — interní poznámky admina
  • • Řádky klikací → odkaz na detail stránku
  • • Nový sloupec "Faktura" (číslo + ikona stavu platby/splatnosti)
  • • **`sale_confirmed`** — výzva makléři po potvrzení prodeje adminem (dynamický předmět, deadline 14 dní, nabídková cena)
  • • **`payment_reminder`** — upomínka o neuhrazené faktuře (číslo, částka, dny po splatnosti, varování o suspenzi po 30 dnech)
  • • Přesný provizní vzorec s příkladem výpočtu
  • • Pravidla pro smlouvu (7 bodů)
  • • Povinnosti makléře + sankce (nenahlášení, nepravdivé údaje, neuhrazení)
  • • Informační klauzule o monitoringu katastru (veřejný registr, souhlas nepotřeba)
  • • Business proces (7 kroků s lhůtami)
27. ledna 2026
  • Fix: Error handling - toast notifikace na zbývajících 9 místech
  • • `fetchProfile` — Supabase error po vyčerpání retries → "Nepodařilo se načíst profil. Zkuste obnovit stránku."
  • • `fetchProfile` — neočekávaná výjimka → "Nepodařilo se načíst profil. Zkuste obnovit stránku."
  • • `signOut` — chyba při odhlášení → "Chyba při odhlašování."
  • • `fetchData` — načtení sítí selhalo → "Nepodařilo se načíst sítě."
  • • `toggleNetworkActive` — přepnutí stavu → "Nepodařilo se změnit stav sítě."
  • • `deleteNetwork` — smazání sítě → "Nepodařilo se smazat síť."
  • • `rejectCustomNetwork` — zamítnutí → "Nepodařilo se zamítnout síť."
  • • `fetchUsers` — načtení uživatelů → "Nepodařilo se načíst uživatele."
  • • `fetchMatches` — načtení spárování → "Nepodařilo se načíst spárování."
  • • `getSession` error — uživatel je přesměrován na login
  • • Init error / timeout — fallback na `unauthenticated` funguje
26. ledna 2026
  • Feat: Kontrola duplicitního telefonu při registraci
  • • **Partial UNIQUE index** na `profiles.phone` (WHERE phone IS NOT NULL)
  • • Vyčištěny existující duplicity (8+2 účtů se sdíleným telefonem)
  • • **POST `/api/check-availability`** - kontrola dostupnosti telefonu
  • • **Seller + Broker** - phone check před `supabase.auth.signUp()`
  • • Pokud telefon obsazen → chybová hláška "Tento telefon je již registrován"
  • • Pokud API selže → pokračuje (DB UNIQUE constraint je safety net)
  • • **8 nových testů** v `__tests__/api/check-availability.test.ts`
  • Feat: Pozastavení účtu (Admin Suspension)
  • • Nové sloupce v `profiles`: `status` ('active'/'suspended'), `suspension_reason`, `suspended_at`, `suspended_by`
  • • CHECK constraint na status
  • • Partial index `idx_profiles_status` (WHERE status != 'active')
  • • **`is_user_active(user_id)`** - SECURITY DEFINER helper funkce
  • • RLS INSERT policies na `bids` a `leads` rozšířeny o `AND is_user_active(auth.uid())`
  • • `get_users_with_email()` rozšířena o `status` a `suspended_at`
  • • **User detail** (`/admin/users/[id]`):
  • • **Users list** (`/admin/users`):
  • • **useAuthGuard** - nový flag `isSuspended` (admin role vyjmuta)
  • • **Dashboard** - varovný banner (červený gradient) pokud pozastaven
  • • **Broker bid submit** - blokace odeslání nabídky + disabled tlačítko
  • • **Seller new lead** - blokace vytvoření poptávky + disabled tlačítko
  • • **`/auth/suspended`** (nová stránka) - info o pozastavení, důvod, kontakt, odhlášení
  • • Rozšířeny typy `Profile` (Row/Insert/Update) o suspension pole
  • • Nová funkce `is_user_active` ve Functions typech
  • Admin: Sorting + Pagination na všech admin tabulkách
  • • **`hooks/useAdminTable.ts`** - generický hook pro sorting + pagination stav
  • • **`hooks/useDebounce.ts`** - debounce pro search inputy (300ms default)
  • • **`components/sortable-table-head.tsx`** - klikatelný TableHead se sort ikonami
  • • **`components/table-pagination.tsx`** - paginační ovládání pod tabulkou
  • • **Users** - sortovatelné: Jméno, Role, Firma, Registrace + debounced search
  • • **Leads** - sortovatelné: Typ, Město, Prodávající, Stav, Nabídky, Vytvořeno
  • • **Bids** - sortovatelné: Odhad ceny, Provize, Stav, Datum; odstraněn ADMIN_PAGE_LIMIT
  • • **Matches** - sortovatelné: Datum, Odhad ceny, Provize, Stav prodeje; odstraněn ADMIN_PAGE_LIMIT
  • • `DEFAULT_ADMIN_PAGE_SIZE = 20` (nová)
  • • `ADMIN_PAGE_LIMIT` ponechán pro zpětnou kompatibilitu
  • 🧹 ESLint: Oprava všech 80 problémů (6 errors + 74 warnings)
  • • `bid-comparison-table.tsx` — `set-state-in-effect` → lazy useState initializer (nahrazuje useEffect)
  • • `photo-lightbox.tsx` — `set-state-in-effect` → "adjusting state during render" pattern
  • • `price-input.tsx` — `set-state-in-effect` → displayValue odvozeno přímo z prop (bez useState/useEffect)
  • • `useAuthGuard.ts` — `set-state-in-effect` (2×) → eslint-disable (redirect flag potřebuje useState pro rendering)
  • • ~45× `import/order` — automaticky opraveno (`eslint --fix`)
  • • 9× nepoužívané proměnné/importy odstraněny
  • • 7× `no-img-element` → eslint-disable (lightbox/upload blob URL)
  • • 1× `alt-text` → přejmenování Lucide `Image` → `ImageIcon`
  • • 2× `exhaustive-deps` → eslint-disable
  • • 1× `import/no-duplicates` → eslint-disable-line (leaflet namespace import)
  • • 2× nepoužívané props odstraněny z `CollapsibleCategorySection`
  • • 1× TypeScript fix admin/bids (Supabase relace jako pole → transformace broker i lead)
  • ⚡ Code Quality Overhaul - Komplexní audit a hardening (17 úkolů ve 4 fázích)
  • 🔒 ZÁSADNÍ: Security Hardening
  • • **Problém**: `/api/test-watchdog` bez jakékoliv autentizace - kdokoli mohl volat ČÚZK API
  • • **Řešení**: JWT token + admin role kontrola přes Supabase server client
  • • Soubor: `frontend/src/app/api/test-watchdog/route.ts`
  • • **Problém**: `watchdog-check` Edge Function posílala `service_role_key` jako Bearer token přes HTTP
  • • **Řešení**: Přímé volání Resend API místo HTTP volání `send-notification-email`
  • • Soubor: `supabase/functions/watchdog-check/index.ts`
  • • Přidáno do `.gitignore`: `tmpclaude-*`, `nul`, `frontend/nul`, `frontend/tmpclaude-*`
  • • Smazáno 29 dočasných souborů z repozitáře
  • • **Nový soubor**: `frontend/src/lib/rate-limit.ts` - in-memory sliding window
  • • Aplikováno na 5 API routes:
  • • Automatický cleanup paměti každých 5 minut
  • 🔒 ZÁSADNÍ: CI/CD Pipeline (z 0/10 na funkční)
  • • **Nový soubor**: `.github/workflows/ci.yml`
  • • Pipeline: `npm ci → eslint → tsc --noEmit → vitest → next build`
  • • Spouští se na push a PR do master
  • • Husky s monorepo setupem (`.git` v root, `package.json` ve `frontend/`)
  • • lint-staged: `eslint --fix` na staged `.ts/.tsx` soubory
  • • Soubor: `frontend/.husky/pre-commit`
  • 🔒 ZÁSADNÍ: Testing (z 0/10 na funkční)
  • • **Nový soubor**: `frontend/vitest.config.ts`
  • • **3 test suites**:
  • • **Extrakce**: IČO validace do sdíleného modulu `lib/utils/ico-validation.ts`
  • Error Handling
  • • `map-picker.tsx` - 2× `.catch(() => ({}))` → `toast.error()`
  • • `seller/new-lead/page.tsx` - LV lookup → `toast.error()`
  • • `profile/page.tsx` - loading networks/templates → `toast.error()`
  • • `admin/matches/page.tsx` - `alert()` → `toast.error()`
  • • Odstraněn debug `console.log` z `test-watchdog/route.ts`
  • Refactoring
  • • **Nový soubor**: `frontend/src/hooks/useFetchData.ts`
  • • Nahrazuje opakující se `useState + useEffect` pattern
  • • Exportován z `hooks/index.ts`
  • • `ANONYMIZE_RADIUS_M = 500` (geo.ts)
  • • `PARCEL_ZOOM_THRESHOLD = 17` (map-picker.tsx)
  • • `COMPRESSION_THRESHOLD = 300 * 1024` (photo-upload.tsx)
  • • `ADMIN_PAGE_LIMIT = 100` (admin stránky)
  • • Soubor: `frontend/src/lib/constants.ts`
  • • `zod` 4.1→4.3, `@supabase/supabase-js` 2.86→2.91, `@types/node` v20→v25
  • • 5 nových indexů pro optimalizaci RLS a častých queries:
  • Polish
  • • `map-picker.tsx` - `role="region" aria-label="Mapa pro výběr lokace nemovitosti"`
  • • `lead-location-map.tsx` - `role="region" aria-label="Mapa s přibližnou polohou nemovitosti"`
  • • **Nový soubor**: `frontend/.prettierrc`
  • • Přidáno: `eslint-config-prettier`, `eslint-plugin-import`
  • • Import sorting pravidla (groups, no duplicates)
  • • Prettier: `semi: true`, `singleQuote: false`, `printWidth: 100`
  • • Nahrazen manuální auth kód za `useAuthGuard({ allowedRoles: ["admin"] })`
  • • Soubor: `frontend/src/app/admin/layout.tsx`
  • • Nahrazen `return params as LeadParams` za field-by-field validaci
  • • Numerické, stringové a boolean pole validovány individuálně
  • • Soubor: `frontend/src/lib/supabase/types.ts`
24. ledna 2026
  • Feat: Admin users - zobrazení emailu
  • • **`get_users_with_email()`** - vrací seznam uživatelů s emailem (join profiles + auth.users)
  • • **`get_user_email_by_id(user_id)`** - vrací email konkrétního uživatele podle ID
  • • Obě funkce kontrolují admin roli volajícího
  • • `/admin/users` - email zobrazen v sloupci Kontakt, vyhledávání rozšířeno o email
  • • `/admin/users/[id]` - email zobrazen v sekci Kontaktní údaje
  • • `supabase/migrations/..._admin_users_email_functions.sql`
  • • `frontend/src/app/admin/users/page.tsx`
  • • `frontend/src/app/admin/users/[id]/page.tsx`
  • Feat: Watchdog - Kompletní implementace automatické detekce plomby
  • • **watchdog-check v4** - kontroluje matche se statusem `watching`
  • • **send-notification-email v12** - nový typ `plomba_detected`
  • • Příjemce: `admin@spravnymakler.cz`
  • • Obsah: typ řízení, lokace, prodávající, makléř, odkaz na LV v ČÚZK
  • • Automatická změna statusu matche na "Prodáno"
  • • `/admin/settings` - Watchdog status změněn na "Aktivní"
  • • `/admin/matches` - upozornění ⚠️ "Chybí k.ú. / parcela" u matchů bez katastrálních dat
  • • `watchdog-weekly-check`: `0 8 * * 1` (pondělí 8:00 UTC)
  • • `supabase/functions/watchdog-check/index.ts`
  • • `supabase/functions/send-notification-email/index.ts`
  • • `supabase/migrations/20260124150000_setup_watchdog_cron.sql`
  • • `frontend/src/app/admin/settings/page.tsx`
  • • `frontend/src/app/admin/matches/page.tsx`
  • Security: Fix function_search_path_mutable
  • • `update_networks_updated_at` - přidán `SET search_path = public`
  • • `update_broker_service_profiles_updated_at` - přidán `SET search_path = public`
  • • `ensure_single_default_service_profile` - přidán `SET search_path = public`
  • • `supabase/migrations/20260124110000_fix_function_search_path.sql`
  • Security: Fix SECURITY DEFINER views
  • • **leads_for_brokers** - změna `security_invoker=false` → `security_invoker=true`
  • • **admin_leads_summary** - změna `security_invoker=false` → `security_invoker=true`
  • • View `admin_leads_summary` přidán do migrací (dříve jen v DB)
  • • `supabase/migrations/20260124100000_fix_security_invoker_views.sql`
23. ledna 2026
  • Design Enhancement - Micro-interactions a vizuální hloubka
  • • **Brand shadows** - nové utility třídy `shadow-brand-sm/md/lg/xl` s fialovým nádechem
  • • **Hover lift** - `.hover-lift` třída pro jemné vyzdvižení při hoveru
  • • **Active press** - `.active-press` třída pro stisknutí při kliknutí
  • • **Animace** - nové keyframes: `subtle-pulse`, `shimmer`, `float`, `bounce-subtle`
  • • Přidán `active:scale-[0.98]` pro všechny varianty (press feedback)
  • • Přidány shadow transitions: `hover:shadow-md` → `active:shadow-sm`
  • • Gradient varianta s `hover:shadow-lg` a fialovým glow efektem
  • • Změna `hover:shadow-md` → `hover:shadow-lg` pro výraznější stín
  • • Přidán `hover:-translate-y-0.5` pro jemný lift efekt
  • • Prodloužena transition na `duration-300`
  • • Brand-colored focus ring: `focus-visible:border-[#6039f6]`
  • • Glow efekt: `focus-visible:shadow-[0_0_0_4px_rgba(96,57,246,0.1)]`
  • • Sticky pozice: `sticky top-0 z-50`
  • • Glass efekt: `backdrop-blur-sm bg-background/95`
  • • Přidán `shadow-sm` pro vizuální separaci
  • • Logo hover: `group-hover:scale-105`
  • • Lucide ikony pro každý status typ (Clock, Eye, CheckCircle, XCircle, TrendingUp/Down...)
  • • Změna na `rounded-full` pro pill shape
  • • Spinning animace pro "collecting" stav
  • • Nový prop `hideIcon` pro zobrazení pouze textu
  • • Nový prop `icon` s možnostmi: inbox, search, question, alert
  • • Animovaný icon container s gradient pozadím (`animate-float`)
  • • Změna na `border-2 border-dashed` styl
  • • Gradient CTA tlačítko
  • • Přidán `hover:-translate-y-1 hover:shadow-lg` pro výraznější lift
  • • Ready state: `ring-2 ring-green-200 shadow-green-100`
  • • Urgency indicator: `animate-subtle-pulse` při ≤2 zbývajících slotech
  • • Přidán `shadow-sm` base shadow
  • • Interaktivní badges: `[a&]:hover:shadow-md [a&]:hover:scale-105`
21. ledna 2026
  • Brand Design 2025 - Kompletní vizuální redesign
  • • **S-shaped ikona** s textem "Správný Makléř"
  • • Gradient na ikonu: lavender → purple → dark
  • • Bílá verze pro tmavé pozadí (`logo-white.svg`)
  • • Favicon a PWA ikony aktualizovány
  • • **Royal Plum**: `#220860` (primární tmavá)
  • • **Brand Purple**: `#6039F6` (gradient střed)
  • • **Lavender Glow**: `#AF96FC` (accent světlá)
  • • **Text**: `#1f1463` (tmavě modrá)
  • • Gradient pozadí: `from-[#220860] via-[#6039f6] to-[#AF96FC]`
  • • Bílé logo, bílý text, bílá tlačítka
  • • Modernizovaný vizuální styl
  • Design: Gradient top borders na všech Card komponentách
  • • Přidány gradient top borders na všechny Card komponenty v aplikaci
  • • 74 karet aktualizováno ve 29 souborech
  • • Brand gradient: `from-[#220860] via-[#6039f6] to-[#AF96FC]`
  • • **Success** (zelená): email-verified, úspěšné akce
  • • **Error** (červená): chybové stavy
  • • **Pending** (oranžová): čekající položky (admin networks)
  • • Admin: dashboard, users, leads, bids, matches, networks, settings + detaily
  • • Seller: dashboard, new-lead wizard (5 kroků), lead detail, lead edit
  • • Broker: dashboard, lead detail, bids, match detail
  • • Auth: login, register, verify, forgot-password, reset-password, email-verified
  • • Public: terms, terms/broker, privacy, changelog
  • Legal: Údaje provozovatele aktualizovány
  • • **IČO**: 24109193
  • • **Sídlo**: Plzeňská 3352/156, Smíchov, 150 00 Praha 5
  • • **E-mail**: info@spravnymakler.cz
  • • Footer na všech stránkách
  • • Terms of Service (`/terms`)
  • • Privacy Policy (`/privacy`)
  • • GDPR dialogy v registraci
  • Feat: Watchdog příprava - nová databázová pole
  • • `cadastral_code` - kód katastrálního území (pro ČÚZK API)
  • • `house_number` - číslo popisné/evidenční (pro lookup jednotek)
  • • Příprava pro automatické sledování změn na LV (plomba)
  • • Umožní přesnější lookup jednotek v ČÚZK REST API KN
  • • Watchdog CRON job: kontrola prodeje každé pondělí 8:00 UTC
25. prosince 2025
  • Fix: Deploy opravy a Edge Functions
  • • **admin/matches/page.tsx** - Supabase 1:1 relace (`lead`, `bid`) vracely pole místo objektu
  • • Přidána kontrola `Array.isArray()` s fallbackem na první prvek
  • • Oprava přístupu k `icon` property pro "all" status filter
  • • **Příčina**: Default hodnota `matches.status` byla `'active'`, ale check constraint povoloval pouze `watching/sold/invoiced/paid/cancelled`
  • • **Řešení**: DB migrace - změna default hodnoty na `'watching'`
  • • `send-notification-email` v11
  • • `send-verification-email` v6
  • Admin: Stránka Spárování - kompletní redesign
  • • **Sledováno** (modrá) - čeká se na prodej
  • • **Prodáno** (zelená) - detekována plomba na LV
  • • **Fakturováno** (oranžová) - faktura odeslána
  • • **Zaplaceno** (fialová) - platba přijata
  • • **Zrušeno** (šedá) - zrušený případ
  • • **Naše provize** - kalkulace: `Odhad ceny × Provize makléře × 20%`
  • • **Odkaz na LV** - klikací odkaz na ČÚZK nahlížení do katastru
  • • Editovatelný select pro změnu stavu (admin only)
  • • Filtry podle stavu prodeje (taby s počty)
  • • Klikací odkazy: Nemovitost → lead detail, Prodávající → profil, Makléř → profil
  • • Přechod z nested queries na snapshot sloupce (performance)
  • • Nová RLS policy pro UPDATE matches (admins only)
  • • DB migrace: `status` CHECK constraint + workflow stavy
  • UX: Profil - sidebar tabs layout
  • • **Broker (5 tabů)**: Profil, Firma, Notifikace, Šablony, Účet
  • • **Seller (3 taby)**: Profil, Notifikace, Účet
  • • **Profil** - osobní údaje (jméno, email, telefon)
  • • **Firma** (broker) - firemní údaje, příslušnost k síti, adresa kanceláře (sloučeno)
  • • **Notifikace** - preferované oblasti (broker) + emailové notifikace
  • • **Šablony** (broker) - šablony služeb pro rychlejší vyplňování nabídek
  • • **Účet** - zabezpečení (změna hesla) + nebezpečná zóna (smazání účtu)
  • • Vertikální sidebar navigace vlevo, obsah vpravo
  • • Na mobilu ikony bez textu (responzivní)
  • • Adresa kanceláře přesunuta z vlastního tabu do tabu "Firma"
  • • Radix Tabs s `orientation="vertical"`
  • Feat: Změna hesla v profilu
  • • Nová sekce "Zabezpečení" v tabu Účet
  • • Dialog pro změnu hesla s validací aktuálního hesla
  • • Kontrola délky nového hesla (min. 6 znaků)
  • Feat: Re-verifikace telefonu při změně
  • • Oranžové varování při změně telefonního čísla
  • • Reset `phone_verified_at` na null při uložení
  • • Platí pro seller i broker (telefon se předává při matchi)
  • Feat: Jedna cena místo rozsahu
  • • **Formulář nabídky**: Jedno pole "Odhadovaná prodejní cena" místo dvou (Od/Do)
  • • **Databáze**: `price_min` = `price_max` (zpětná kompatibilita)
  • • **Zobrazení**: `formatPriceRange()` zobrazí jednu cenu když min === max
  • • **Existující data**: Aktualizována na horní hodnotu rozsahu
  • • Jednodušší rozhodování pro prodávajícího
  • • Větší tlak na makléře být přesný
  • • Inspirace: Online odhady (Hypox) také uvádějí jednu cenu
  • Feat: Cenový semafor - vylepšení
  • • **Medián** z 5 nabídek (ne průměr - není ovlivněn extrémy)
  • • **Trigger až při 5. nabídce** - do té doby klient nevidí porovnání
  • • **Klasifikace**:
  • • **Vysoká cena**: "Tato cena je výrazně vyšší než ostatní nabídky. Makléř možná přestřelil, aby vypadal atraktivně."
  • • **Nízká cena**: "Tato cena je výrazně nižší než ostatní nabídky. Makléř možná podstřelil nebo nemá zkušenosti s touto lokalitou."
  • • `calculate_price_label()` - výpočet s mediánem
  • • `recalculate_lead_price_labels()` - přepočet všech nabídek leadu
  • • `trigger_calculate_price_labels_at_max_bids()` - trigger při dosažení 5 nabídek
  • UX: Mobile-friendly srovnávací tabulka
  • • **Tlačítko "Skrýt"** v řádku "Akce" u každého makléře
  • • Skryje makléře z porovnání (neovlivňuje databázi)
  • • **Toast notifikace** s počítadlem "X makléřů skryto" a tlačítkem "Vrátit"
  • • Možnost vrátit všechny skryté makléře jedním klikem
  • • UX: Méně děsivé než "Vyřadit" - uživatel ví že akce je vratná
  • • **Agregované řádky** pro každou kategorii (Příprava a Vizuál, Marketing, Právní, Poprodejní)
  • • **Počítadlo X/Y** s progress barem (zelená výplň)
  • • **Kliknutím rozbalení** všech služeb v kategorii
  • • **Responzivní chování**:
  • • **Tlačítko "Sbalit vše"** pro rychlé sbalení všech kategorií
  • • **Nový řádek "Vybrat makléře"** na konci tabulky
  • • **Sticky pozice** - viditelný při scrollu
  • • **Zelené pozadí** pro vizuální odlišení od ostatních řádků
  • • Tlačítka "Vybrat" přesunuta sem (pryč z hlavičky)
  • • Nová závislost: `usehooks-ts` (useMediaQuery hook)
  • • Integrace: `sonner` toast komponenta (shadcn/ui)
  • • Responsive breakpoint: 768px (md)
  • UX: Dvou-krokové potvrzení výběru makléře
  • • **První klik** → tlačítko se změní na červené "Opravdu?"
  • • **Druhý klik do 3 sekund** → provede se match
  • • **Timeout 3s** → automatický reset zpět na "Vybrat"
24. prosince 2025
  • UX: Srovnávací tabulka nabídek makléřů (styl Alza.cz)
  • • **Sloupce = makléři** (#1-#5), řádky = služby seskupené do kategorií
  • • **Hlavička**: odhad ceny, provize, tlačítko "Vybrat"
  • • **28 služeb ve 4 kategoriích** s ✓/✗ ikonami
  • • **Toggle "Zobrazit pouze odlišné parametry"** - filtruje služby kde se makléři liší
  • • **Žluté zvýraznění** rozdílných řádků
  • • **Popover pro zprávy** - kliknutím na zkrácený text se zobrazí celá zpráva
  • • **Mobile-friendly**:
  • UX: Barevné stavy poptávek pro prodávající
  • • **Šedá** (waiting) - Čeká na nabídky (0 nabídek)
  • • **Žlutá** (collecting) - Sbírá nabídky (1-4 nabídek)
  • • **Zelená** (ready) - Připraveno k výběru (5/5 nabídek) + zelený ring
  • • **Modrá** (matched) - Spárováno
  • • `constants.ts` - nový `SELLER_LEAD_STATUS_CONFIG` a funkce `getSellerLeadDisplayStatus()`
  • • `status-badge.tsx` - podpora typu "seller-lead"
  • • `lead-card.tsx` - progress bar X/5, dynamický badge, `maxBids` prop
  • • `seller/leads/page.tsx` - filtrovací taby (Všechny, Sbírá nabídky, K výběru, Spárováno)
  • KRITICKÁ OPRAVA: Radix Checkbox Bug #2549 - infinite loop uvnitř form
  • • **Problém**: React Error #185 při klikání na checkbox služeb v broker lead detail page
  • • **Příčina**: Radix Checkbox uvnitř `<form>` dispatchuje syntetické click eventy přes interní `BubbleInput`
  • • **Proč fungovalo v profile page**: ServiceCheckboxes tam byl uvnitř `<Dialog>`, NE uvnitř `<form>`
  • • **Řešení**:
  • • **Reference**: https://github.com/radix-ui/primitives/issues/2549
  • KRITICKÁ OPRAVA: React Error #185 - Maximum update depth exceeded
  • • **Problém**: Při rychlém zaškrtávání služeb v nabídkovém formuláři došlo k pádu aplikace
  • • **Příčina**: Kombinace několika faktorů způsobovala kaskádové re-rendery:
  • • **Řešení** (3 commity):
  • UX: Accordion pro služby makléře
  • • **Nahrazen scrollbox** - služby nyní v rozbalovacích sekcích (Accordion)
  • • **4 kategorie jako AccordionItem** - Příprava a Vizuál, Marketing, Právní servis, Poprodejní servis
  • • **Checkbox v hlavičce** - kliknutím vybere/odebere celou kategorii
  • • **Počítadlo X/Y** u každé kategorie
  • • **Žádný horizontální scroll** - jeden sloupec, čistý design
  • • **Automatické otevření** - kategorie s vybranými službami se otevřou
  • • **Nová závislost**: `@radix-ui/react-accordion`
  • Služby makléře - checkboxy místo textu
  • • **BROKER_SERVICES** - 28 služeb ve 4 kategoriích:
  • • **ALL_BROKER_SERVICES** - flat array všech služeb
  • • **BROKER_SERVICE_LABELS** - mapa klíč → label
  • • **BrokerServiceKey** - TypeScript union type
  • • **ServiceCheckboxes** - grid s checkboxy seskupenými do kategorií
  • • **ServiceBadges** - kompaktní zobrazení jako barevné tagy
  • • **Migrace**: `add_broker_services_and_profiles`
  • • **Nové sloupce v `bids`**:
  • • **Nová tabulka `broker_service_profiles`**:
  • • **broker/leads/[id]** - nabídkový formulář
  • • **profile** - správa šablon služeb (pouze broker)
  • • **seller/leads/[id]** - zobrazení nabídek
  • • **admin/leads/[id]** - admin panel
  • • Aktualizovány typy v `lib/supabase/types.ts`
  • • Nový type alias `BrokerServiceProfile`
  • • Oprava: `as string[]` cast v service-checkboxes.tsx (type mismatch)
  • • Oprava: `const brokerId = profile.id` před async funkcí (null check)
23. prosince 2025
  • Code Review a Optimalizace
  • • **XML Injection ochrana** - `escapeXml()` funkce pro VIES SOAP API (`ico-lookup/route.ts`)
  • • **Validace parcelního čísla** - regex validace proti injection útokům (`cadastral-lookup/route.ts`)
  • • **signOut() race condition** - správné pořadí: nejdřív Supabase logout, pak clear state (`AuthContext.tsx`)
  • • **fetchProfile memory leak** - přidán `mountedRef` check + exponential backoff retry (`AuthContext.tsx`)
  • • **photo-upload setTimeout leak** - cleanup timer při unmount (`photo-upload.tsx`)
  • • **N+1 query fix** - Map lookup O(1) místo .filter() O(n) v loop (`dashboard/page.tsx`)
  • • **ARES timeout** - přidán 10s timeout pomocí `fetchWithTimeout()` (`ico-lookup/route.ts`)
  • • **Geo cache** - zvýšen z 1 hodiny na 7 dní pro RUIAN data (`cadastral-lookup/route.ts`)
  • • **Leaflet CSS utility** - nový soubor `lib/utils/leaflet.ts` s `initializeLeaflet()` a `ensureLeafletCSSLoaded()`
  • • **EnergyRatingSelect** - nová reusable komponenta pro PENB výběr (nahrazuje 3 duplicity)
  • • **UnitNumberInput** - nová reusable komponenta pro číslo jednotky (nahrazuje 3 duplicity)
  • Vrstva parcel v MapPicker - WMTS upgrade
  • • **Nové tlačítko "Parcely"** v pravém horním rohu mapy
  • • **ČÚZK WMTS vrstva** (nahrazuje WMS) - ostré rozlišení až do zoom 19
  • • **REST endpoint**: `services.cuzk.cz/wmts/local-km-wmts-google/rest/WMTS/default/KN/{z}/{y}/{x}`
  • • **Použití**: Pro snadnější identifikaci pozemku (garáže, pole, zahrady)
  • • **Omezení**: Zobrazí se pouze při zoom 17+ (WMTS limit)
  • • **UX**: Kliknutí zachová aktuální zoom (nepřibližuje pokud zoom ≥ 17)
  • • **Marker**: Vysoký z-index (1000) aby byl viditelný nad vrstvou parcel
  • • **Pouze pro prodávající**: Makléři stále vidí jen anonymní kruh
  • Admin detail poptávky - rozšíření a editace
  • • **Kompletní zobrazení všech polí** - LV, parcelní číslo, číslo jednotky, dispoziční řešení, vlastnictví, stav, typ budovy
  • • **Editace katastrálních údajů** - parcelní číslo, LV, číslo jednotky (inline edit s uložením)
  • • **Editace detailů nemovitosti** - dispozice, vlastnictví, stav, typ budovy
  • • **Editace popisu** - inline editace textu poptávky
  • • **Správa fotek**:
  • • **Oprava duplicitní adresy** - sloučeno "Přesná adresa" a "Adresa" do jedné sekce
  • UX vylepšení - Číslo jednotky
  • • **Přesunuto pole "Číslo jednotky"** z Kroku 2 (Poloha) do Kroku 3 (Parametry)
  • • **Lepší viditelnost** - pole bylo dříve schované na konci kroku Poloha
  • • **Shrnutí** - číslo jednotky se nyní zobrazuje v přehledu (Krok 5)
  • Oprava: LV jednotky se nyní správně získává
  • • **Problém**: Když uživatel zadal číslo jednotky v Kroku 3, LV jednotky se nikdy nezískalo z ČÚZK API
  • • **Příčina**: API se volalo pouze v Kroku 2 (pro parcelu), ne po zadání čísla jednotky
  • • **Řešení**:
  • • **Jak to funguje**:
  • • **Pro domy**: LV se získá automaticky v Kroku 2 při výběru adresy (není potřeba číslo jednotky)
  • KRITICKÁ OPRAVA: ČÚZK REST API KN autentizace
  • • **Problém**: LV číslo se nikdy nezískávalo - API vracelo 401 Unauthorized
  • • **Příčina**: Špatný název hlavičky `Api-Key` místo `ApiKey` (bez pomlčky)
  • • **Dopad**: Všechny typy nemovitostí (byty, domy, pozemky) neměly LV
  • • **Řešení**:
  • • **Nyní funguje**: LV pro parcely i jednotky se správně získává
  • Oprava TypeScript typů
  • • **Regenerace Supabase typů** - oprava chyby s `DatabaseWithoutInternals`
  • • **Nové type aliasy**: `Profile`, `Lead`, `LeadForBroker`, `Bid`, `Match`, `Network`
  • • **Composite typy**: `BidWithLead`, `BidWithBroker`
  • • **Enum typy**: `NotifyNewLeads`, `UserRole`
  • • **Helper funkce**: `parseLeadParams()` pro bezpečné parsování JSONB params
  • Oprava Changelog stránky pro produkci
  • • **Problém**: Changelog stránka nefungovala na Coolify (špatná cesta k souboru)
  • • **Řešení**:
  • • **Funguje** v lokálním vývoji i na produkci
  • Automatické získávání katastrálních dat z ČÚZK
  • • **Parcelní číslo** - automaticky získáváno při výběru lokace
  • • **List vlastnictví (LV)** - automaticky získáváno pro parcely i jednotky
  • • **Číslo jednotky** - nové volitelné pole pro byty, kanceláře, garáže
  • • **API `/api/cadastral-lookup`** rozšířeno:
  • • **Databázové změny**:
  • • **Environment variable**: `CUZK_API_KEY` pro REST API KN
  • • **MapPicker komponenta** - rozšířena o nová pole z ČÚZK
  • • **Formulář new-lead** - zobrazuje katastrální údaje v zeleném boxu
  • Adresa kanceláře a příslušnost k síti
  • • **Adresa kanceláře (působiště)** - nové povinné pole pro makléře
  • • **Příslušnost k realitní síti** - nová funkcionalita
  • • **Nová tabulka `networks`**
  • • **Admin panel `/admin/networks`**
  • • **Databázové změny**
  • • **Frontend změny**
  • Fakturační údaje makléře
  • • **Sídlo firmy** - nové pole v registraci a profilu (readonly, načítá se z ARES)
  • • **Bankovní účet** - volitelné pole pro makléře (formát: 123456789/0100)
  • • **Databázová změna** - nové sloupce `company_address` a `bank_account` v tabulce `profiles`
  • • **Registrace makléře** - sídlo firmy se zobrazí automaticky po ověření IČO
  • • **Profil makléře** - sekce "Firemní údaje" rozšířena o sídlo a bankovní účet
  • • **API `/api/ico-lookup`** - již vracelo adresu z ARES, nyní se využívá i ve frontendu
  • Rozdělení jména na Jméno + Příjmení
  • • **Databázová změna** - nové sloupce `first_name` a `last_name` v tabulce `profiles`
  • • **Migrace dat** - automatické rozdělení `full_name` podle první mezery
  • • **SQL funkce `create_match()`** - upravena pro snapshoty (spojuje first_name || ' ' || last_name)
  • • **VIEW `admin_leads_summary`** - aktualizován pro spojené jméno
  • • **14 frontend souborů upraveno**:
  • • **Zpětná kompatibilita** - `full_name` zůstává jako deprecated, stále se ukládá pro fallback
  • • **TypeScript typy** - aktualizovány + helper funkce `formatFullName()`
  • Vylepšení ověření makléřů
  • • **IČO ověření v profilu** - makléř musí ověřit IČO i při změně v profilu (stejně jako při registraci)
  • • **VIES ověření plátcovství DPH** - integrace EU VAT Information Exchange System
  • • **Readonly firemní údaje** - název firmy a DIČ nelze ručně upravovat
  • UX vylepšení
  • • **Odstranění duplicitního odkazu** - karta "Profil makléře" odstraněna z dashboardu makléře
  • Technické změny
  • • **API `/api/ico-lookup`** - rozšířeno o VIES ověření
  • • **Profil stránka** - přidány stavy pro IČO ověření (`icoVerified`, `icoLoading`, `icoError`)
22. prosince 2025
  • Migrace hostingu: Vercel → Hetzner + Coolify (DOKONČENO)
  • • **Nový hosting**: Hetzner VPS (CPX32) + Coolify v Německu
  • • **Důvod migrace**:
  • • **Server specifikace**:
  • • **Coolify**: Self-hosted PaaS (open-source Vercel alternativa)
  • Vercel zrušen
  • • **Projekt smazán** z Vercel dashboardu
  • • **Supabase Auth** - aktualizovány redirect URLs na `spravnymakler.cz`
  • • **Referer headery** - aktualizovány v API routes pro Mapy.cz
  • • **GDPR dokumentace** - aktualizováno v privacy policy a registračních formulářích (Vercel → Hetzner)
  • • **Admin settings** - Vercel Dashboard nahrazen Coolify Dashboard
  • Aktualizované soubory
  • • `api/address-suggest/route.ts` - Referer → spravnymakler.cz
  • • `api/reverse-geocode/route.ts` - Referer → spravnymakler.cz
  • • `privacy/page.tsx` - Hetzner místo Vercel
  • • `admin/settings/page.tsx` - Coolify Dashboard
  • • `auth/register/seller/page.tsx` - Hetzner v GDPR info
  • • `auth/register/broker/page.tsx` - Hetzner v GDPR info
  • Next.js konfigurace pro self-hosted
  • • **Standalone output** - `output: "standalone"` v next.config.ts
  • • **Vypnutá image optimizace** - `images.unoptimized: true` (řeší 400 Bad Request)
  • Dokumentace
  • • **MIGRATION-PLAN.md** - kompletní návod pro případnou budoucí migraci
  • • Aktualizace CLAUDE.md, CHANGELOG.md, ROADMAP.md
19. prosince 2025
  • PENB - Energetická třída budovy
  • • **Nové pole při zadání poptávky** pro byt, dům a komerční nemovitosti
  • • Klasifikace A–G s barevným kódováním podle české legislativy
  • • Volitelné pole (prodávající nemusí vyplnit)
  • • **Zobrazení na detail stránkách** (seller, broker, admin) - barevný badge s třídou
  • • Nové konstanty: `ENERGY_RATINGS`, `ENERGY_RATING_CONFIG`, `ENERGY_RATING_LABELS`
  • Vlastnická omezení nemovitosti
  • • **Nové pole** pro zadání právních/finančních omezení nemovitosti
  • • Radio výběr: "Bez omezení" / "Ano, má omezení"
  • • **Kategorizované checkboxy** pro specifická omezení:
  • • Implementováno v new-lead, edit stránce a všech detail stránkách
  • • Zelený badge "Bez omezení" / Oranžový "Má omezení" + tagy specifikace
  • • Nové konstanty: `PROPERTY_ENCUMBRANCES`, `ENCUMBRANCE_LABELS`
  • • Nové typy: `has_encumbrances`, `encumbrances[]` v LeadParams
  • UX vylepšení - pořadí polí ve formuláři
  • • **Příslušenství před vlastnickými omezeními** - logičtější flow
  • • Nejdřív fyzické vlastnosti, pak právní stav, pak popis
18. prosince 2025
  • Pokročilé filtry pro makléře
  • • **Nová komponenta `LocationFilter`** - hierarchický multi-select pro lokality
  • • **Tabs filtr podle stavu nabídky** na `/broker/leads`
  • • **Nové shadcn komponenty**: Popover, Checkbox, Collapsible
  • Konsolidace kontextových souborů
  • • **Nový `CLAUDE.md`** - jeden konsolidovaný kontextový soubor pro AI
  • • **Cleanup** - staré duplicitní soubory odstraněny (zůstávají v git historii)
17. prosince 2025
  • Rozšíření dat pro makléře
  • • **View `leads_for_brokers`** - přidány chybějící sloupce:
  • • **Migrace**: `20251217215805_add_missing_columns_to_leads_for_brokers_view.sql`
  • Kompletně přepracovaný detail poptávky pro makléře
  • • **Broker detail UI** (`/broker/leads/[id]`) - makléři nyní vidí všechny relevantní údaje:
  • • **Lokace** - zobrazena jako samostatný blok s okresem a krajem
  • • **Příslušenství** - kompletní seznam (balkón, lodžie, terasa, zahrada, sklep, parkování, garáž, výtah, bezbariérový, bazén)
  • • **Vlastnosti domu** - nízkoenergetický, dřevostavba, přízemní/patrový, samostatný/řadový
  • TypeScript typy
  • • Aktualizované typy v `types.ts` pro `leads_for_brokers` view
  • • Rozšířený `BrokerLead` type v broker detail stránce
10. prosince 2025
  • Editace a zrušení poptávky (Seller)
  • • **Nová stránka `/seller/leads/[id]/edit`** - editace poptávky
  • • **Tlačítko "Zrušit poptávku"** na detailu
  • • **Nový status `cancelled`** v LEAD_STATUS_CONFIG
  • UX přeuspořádání detailu poptávky
  • • **Nové pořadí sekcí** (podle priority pro uživatele):
  • • **Rationale**: Seller primárně čeká na nabídky, detail už zná
  • KRITICKÁ OPRAVA: leads_for_brokers VIEW
  • • **Problém**: Makléři neviděli žádné poptávky (0 z 0)
  • • **Příčina**: VIEW měl `security_invoker=true` - RLS na leads/bids blokoval dotazy
  • • **Řešení**: Změna na `security_invoker=false` (SECURITY DEFINER)
  • • VIEW nyní bypassuje RLS, bezpečnost zajištěna WHERE clause
  • • Migrace: `fix_leads_for_brokers_security`
  • GDPR a právní dokumenty
  • • **Privacy Policy** (`/privacy`) - kompletní zásady ochrany osobních údajů
  • • **Podmínky služby** (`/terms`) - obecné podmínky pro všechny uživatele
  • Registrace - modální okna pro právní dokumenty
  • • **Seller registrace** - Podmínky a Privacy se otevírají v modálním okně
  • • **Broker registrace** - Privacy se otevírá v modálním okně
  • Čestné prohlášení o neexkluzivitě
  • • **Wizard krok 5** - nový checkbox před odesláním poptávky
  • • **Databáze** - nové sloupce v `leads`:
  • Detail poptávky pro prodávajícího
  • • **Kompletní přehled zadaných údajů** - seller nyní vidí všechny informace které zadal
  • Lightbox pro fotografie
  • • **Nová komponenta `PhotoGallery`** - moderní prohlížení fotek
  • • **Nová komponenta `PhotoLightbox`** - modal dialog pro zobrazení
  • • **Použito na stránkách**:
  • Oprava Leaflet mapy z-index
  • • **Problém**: Mapa se zobrazovala nad lightbox overlayem
  • • **Řešení**: Reset z-indexu pro `.leaflet-pane`, `.leaflet-control`, `.leaflet-control-zoom`
  • • Opraveno v `LeadLocationMap` i `MapPicker` komponentách
  • Broker lead detail - oprava načítání dat
  • • Změna z `leads` tabulky na `leads_for_brokers` view
  • • Správné RLS - broker vidí pouze povolená data
  • Reset hesla
  • • **Nová stránka `/auth/forgot-password`** - zadání emailu pro obnovení hesla
  • • **Nová stránka `/auth/reset-password`** - zadání nového hesla
  • • **Proxy aktualizován** - propouští reset-password a forgot-password bez Basic Auth
  • Next.js 16 migrace
  • • **Middleware → Proxy** - migrace na nový Next.js 16 systém
9. prosince 2025
  • Oprava nahrávání fotek (PhotoUpload) - KRITICKÁ
  • • **Bug #1**: Prohlížeč/mobil zamrzal při nahrávání fotek
  • • **Bug #2**: Fotky se nenahrávaly (soubory neprošly validací)
  • • **Bug #3**: Multiple GoTrueClient instances (stovky instancí)
  • • **Vylepšení**:
  • • **Nová závislost**: `heic2any`
  • UX vylepšení pro makléře
  • • **Odstranění "Lokalita pro makléře"** - pole odstraněno z formuláře i zobrazení
  • • Makléři nyní vidí pouze mapu s anonymním kruhem a okres/kraj
  • • Text u mapy změněn na "Nemovitost se nachází někde ve vyznačeném kruhu"
  • Oprava mapování okres/kraj z Mapy.cz API
  • • **Příčina problému**: V ČR jsou oba (okres i kraj) typu `regional.region`
  • • **Opravené soubory**:
  • • Nyní se data ukládají správně: `district` = okres, `region` = kraj
  • Filtry pro makléře
  • • **Dva filtry** na stránce `/broker/leads`:
  • • Karty poptávek zobrazují "okres, kraj" pod názvem typu nemovitosti
  • Formulář pro pozemky (krok 3)
  • • **Nové pole "Kategorie pozemku"** (povinné):
  • • **Přejmenování**: "Užitná plocha" → "Plocha pozemku" pro typ pozemek
  • • **Zvýšený limit**: max 10 mil m² pro pozemky (vs 100 000 m² pro byty/domy)
  • • **Odstraněny nesmyslné položky** pro pozemky:
  • Databáze
  • • **Nový sloupec**: `leads.land_category` (text)
  • Nové konstanty
  • • `LAND_CATEGORIES` v `lib/constants.ts`
8. prosince 2025
  • Fix: Address suggest API - Referer header
  • • **Problém**: Našeptávač adres vracel 403 "The client did not meet the restrictions"
  • • **Příčina**: Mapy.cz API klíč má omezení na Referer header, server-side requesty neposílaly Referer
  • • **Řešení**: Přidán `Referer: https://spravny-makler.vercel.app/` header do fetch requestu
  • • **Doména REST API**: `api.mapy.com` (ne api.mapy.cz - ta funguje jen pro tiles)
  • Mapy.cz - odstranění OpenStreetMap fallbacku
  • • **address-suggest API route** - odstraněn Nominatim (OSM) fallback
  • • **lead-location-map komponenta** - odstraněn OSM tiles fallback
  • • Projekt nyní používá **výhradně Mapy.cz API**
  • • **Nutné nastavit na Vercelu**:
  • • **API klíč v Mapy.cz portálu** musí mít povolené domény včetně produkční
  • Anonymní kruh pro makléře (Privacy Feature)
  • • **Nový sloupec leads**: `display_latitude`, `display_longitude` - náhodně posunuté souřadnice
  • • **Nová komponenta `LeadLocationMap`** - Leaflet mapa zobrazující 500m kruh
  • • **Nová utility `lib/utils/geo.ts`** - funkce `generateRandomPointInCircle()`
  • • **View `leads_for_brokers` aktualizována**:
  • • Makléř vidí kruh o poloměru 500m, nemovitost je náhodně umístěna uvnitř (ne ve středu)
  • • Offset se generuje jednou při vytvoření poptávky a nemění se
  • Oprava Mapy.cz integrace
  • • **Tiles URL**: `api.mapy.cz` pro mapové dlaždice
  • • **REST API URL**: `api.mapy.com` pro suggest/geocode endpointy
  • • **cityPart v lokaci**: zobrazuje se "Město - Část města" (např. "Plzeň - Plzeň 1")
  • • Přidána proměnná `NEXT_PUBLIC_MAPY_CZ_API_KEY` pro client-side přístup
  • KRITICKÁ OPRAVA #2: supabase-js deadlock bug
  • • **Root cause**: supabase-js má bug kde `await` jakéhokoliv Supabase API volání uvnitř `onAuthStateChange` callbacku způsobí deadlock
  • • **Symptom**: Po SMS ověření spinner točil navždy, Supabase query nikdy nedokončila (HTTP 200, ale Promise se neresolvla)
  • • **Řešení**: `setTimeout(() => fetchProfile(...), 0)` - odloží volání mimo callback
  • • **Reference**: https://github.com/orgs/supabase/discussions/19058
  • KRITICKÁ OPRAVA #1: Supabase SSR caching bug
  • • **Root cause**: `@supabase/ssr` `createBrowserClient` měl interní cache, která vracela stará data i po změnách v DB
  • • **Symptom**: Po ověření emailu/SMS spinner stále točil, UI se neaktualizovalo
  • • **Řešení**: Přechod z `@supabase/ssr` na standardní `@supabase/supabase-js`
  • Změna závislostí
  • • import { createBrowserClient } from '@supabase/ssr'
  • Refaktor auth architektury (dříve)
  • • **Singleton Supabase client** - jedna instance pro celou aplikaci (odstraněny race conditions)
  • • **Nový useAuth hook** - centrální zdroj pravdy pro auth stav
  • • **Smazán useUserRole hook** - nahrazen `useAuth().profile?.role`
  • • **Odstraněna závislost na Realtime** - místo pasivního čekání na změny používáme explicitní invalidaci
  • • **useAuthGuard zjednodušen** - používá useAuth() místo vlastního fetch
  • Opravené problémy
  • • Dashboard se nenačítal po registraci a verifikaci (stale cache)
  • • Více Supabase klientů s různým stavem session
  • • Komponenty nereagovaly na změny profilu po verifikaci
  • Aktualizované soubory
  • • `lib/supabase/client.ts` - změna z @supabase/ssr na @supabase/supabase-js
  • • `AuthContext.tsx` - kompletní přepis (singleton + refetchProfile)
  • • `useAuthGuard.ts` - zjednodušení (používá useAuth)
  • • `role-indicator.tsx` - používá useAuth místo useUserRole
  • • `hero-buttons.tsx` - používá useAuth místo useUserRole
  • • `verify/page.tsx` - visibilitychange místo polling, router.replace()
7. prosince 2025
  • Refaktor registračního procesu
  • • **Jednotná verifikační stránka** `/auth/verify` - email + telefon na jednom místě
  • • **Seller registrace opravena** - telefon povinný, "complete" krok s instrukcemi
  • • **Broker registrace vyčištěna** - odstraněno ~150 řádků mrtvého OTP kódu
  • • **Shared utility** `lib/utils/phone.ts` - normalizePhone funkce
  • • **useAuthGuard** nyní vyžaduje ověření telefonu pro všechny uživatele (seller i broker)
  • • Konzistentní flow: Registrace → Email potvrzení → Login → Verify telefon → Dashboard
  • Mazání účtu s právní ochranou
  • • **Soft delete s anonymizací** - účty se nemažou, data zůstávají pro právní účely
  • • Edge Function `delete-account` s ověřením hesla
  • • Dialog pro mazání účtu v profilu uživatele
  • • **Audit log** - kompletní záloha dat před anonymizací (`account_deletion_audit`)
  • • **Snapshot sloupce v matches** - jméno, telefon, IČO, firma makléře i prodávajícího
  • • Anonymizace: `full_name` = "Smazaný uživatel", osobní údaje = null
  • • Migrované RLS policies filtrují `deleted_at IS NULL`
  • Bezpečnostní opravy
  • • **SECURITY INVOKER** - view `leads_for_brokers` nyní respektuje volajícího
  • • **SET search_path=public** - funkce `create_match` chráněna proti injection
  • • Memory leak fix v broker registraci (interval cleanup)
  • Optimalizace výkonu
  • • **Database views** pro admin panel:
  • • **Pagination** - limit 100 záznamů na admin stránkách
  • • **useMemo** - memoizace filtrovaných seznamů (users, bids, matches)
  • • **Lazy loading** - `browser-image-compression` načítáno dynamicky
  • • **Next.js Image** - remotePatterns pro Supabase Storage
  • Opravy
  • • Fix: Vonage SMS credentials - aktualizace API klíčů
  • • Fix: Sender ID zkráceno na max 11 znaků (limit Vonage)
6. prosince 2025
  • Code Cleanup & Performance
  • • **RLS Performance Fix** - `auth.uid()` → `(select auth.uid())` v 15 policies
  • • **Nová komponenta `LoadingSpinner`** - nahrazuje 7 duplicitních spinnerů
  • • Odstraněn debug `console.log` v photo-upload.tsx
  • • Migrace: `fix_rls_policies_performance`
  • Admin panel
  • • Admin rozhraní s kompletní správou systému
  • • Dashboard se statistikami (uživatelé, poptávky, nabídky, matche)
  • • Seznam uživatelů s filtrováním podle role
  • • Detail uživatele se změnou role a možností smazání
  • • Seznam poptávek s plnými adresami
  • • Detail poptávky se změnou statusu a možností smazání
  • • Správa nabídek (změna statusu, mazání) přímo v detailu poptávky
  • • Stránka spárování s kontaktními údaji obou stran
  • • Nastavení systému s přehledem služeb
  • Email notifikace
  • • Resend integrace - doména ověřena (SPF, DKIM, MX)
  • • Edge Function `send-notification-email`
  • • Supabase Vault secrets (supabase_url, service_role_key)
  • • Edge Function secrets (RESEND_API_KEY, FROM_EMAIL, SITE_URL)
  • • Potvrzení emailu při registraci zapnuto
  • Registrace makléře - právní compliance
  • • **Explicitní souhlas s podmínkami** - viditelné klíčové body (20% provize, zákaz obcházení, mlčenlivost)
  • • **Modal s celým zněním smlouvy** - ScrollArea pro přehledné čtení
  • • **SMS ověření telefonu** - 6místný OTP kód přes Vonage
  • • Normalizace telefonního čísla (+420)
  • • Audit log souhlasu (verze, timestamp, body)
  • • DB sloupce: `terms_accepted_at`, `terms_version`, `terms_explicit_points`, `phone_verified_at`, `ico`
  • IČO ověření a kontrola živnosti
  • • **IČO je povinné** pro registraci makléře
  • • **ARES API integrace** - ověření firmy v registru ARES
  • • API route `/api/ico-lookup` - lookup + RŽP živnosti
  • • Validace kontrolní číslice IČO (modulo 11)
  • • **Auto-fill názvu firmy** po ověření IČO
  • • **Kontrola živnosti** "Realitní zprostředkování" z RŽP
  • • Chybějící živnost = varování (neblokuje registraci)
  • • DB sloupec: `has_realitni_zivnost` (boolean)
  • • Admin může manuálně upravit stav živnosti
  • Opravy
  • • Fix: `commission` → `commission_percent` v admin detailech
  • • RLS policies pro admin přístup (SELECT, UPDATE, DELETE)
  • • Fix: useUserRole hook - automatický update UI při přihlášení/odhlášení
  • • Fix: chybějící závislost @radix-ui/react-alert-dialog
5. prosince 2025
  • Nové funkce
  • • **Location Picker** - výběr polohy na mapě pro pozemky/garáže bez adresy
  • • **Katastrální údaje** - automatické získání k.ú. z ČÚZK RUIAN API
  • • API route `/api/cadastral-lookup` pro katastrální území
  • • API route `/api/reverse-geocode` pro Mapy.cz reverse geocoding
  • • Přepínač "Mám adresu" / "Vybrat na mapě" ve wizardu
  • Změny
  • • DB: street je nullable - pozemky/garáže mohou být bez adresy
  • • AddressAutocomplete vrací i souřadnice (lat/lon)
  • • Wizard step 2 přepracován s přepínačem metody zadání lokace
4. prosince 2025
  • Základní funkce
  • • Registrace prodávající / makléř
  • • Wizard pro zadání nemovitosti (5 kroků)
  • • Fotky interiér/exteriér s kompresí
  • • Address autocomplete (Mapy.cz)
  • • Seznam poptávek pro makléře
  • • Odeslání nabídky
  • • Match systém
  • • Dashboard pro oba typy uživatelů
  • • **Logo** v hlavičce a na homepage
  • • **useAuthGuard hook** - centralizovaná autentizace
  • • **useUserRole hook** - získání role uživatele
  • • **LeadCard komponenta** - reusable karta pro poptávky
  • • Changelog stránka pro testery
  • • Formátování cen s mezerami (1 000 000 Kč)
  • • Dynamická homepage podle role uživatele
  • • Password protection pro staging
  • • **Role indikátor** - barevný pruh (Prodávající/Makléř/Admin)
  • • **Stránka profilu** (`/profile`)
  • • **Filtry podle kraje/okresu** na `/broker/leads`
  • • **Preference makléře** - výběr krajů a frekvence notifikací
  • • Extrakce kraje a okresu z Mapy.cz API
  • Opravy
  • • RLS recursion při vytváření nabídky
  • • Hydration error na homepage
  • • Doplněny kraje/okresy u existujících poptávek
  • • Security issues a useEffect dependencies
  • • N+1 query optimalizace
Typy email notifikací