# 99 - Zaznam zmen Nejnovejsi nahore. ## 2026-09-02 - Tickety maji zalozky, fronta umi prirazovat Seznam ticketu mel dva prepinace, "Moje tickety" a "Ve fronte", schovane mezi ostatnimi filtry - splyvaly s nimi a vypadaly jako dalsi dva chipy. Jenze "co mam u sebe", "co jeste nikdo nema" a "co je ve firme" nejsou filtry, jsou to **tri ruzne prace a kazda chce jinou tabulku**. ### Tri zalozky | Zalozka | Kdo ji ma | Cim se lisi | | ---------------- | ----------------------------- | ------------------------------------ | | Moje tickety | kdo je veden jako resitel | bez sloupce Resi, byl by tam porad on | | Nezarazene | kazdy | radky s rychlym prirazenim | | Vsechny tickety | kdo vidi i cizi tickety | sloupec Resi a filtr na sekce | **Sloupec Resi je jen ve Vsech.** V Mych by ve vsech radcich stalo tyz jmeno a ve fronte je z definice prazdny. Presne o tom mluvil pozadavek: az v prehledu vsech ma smysl videt, kdo co ma u sebe. Zalozka Vsechny se nenabizi tomu, kdo vidi jen svoje - byl by to tentyz seznam. Rika to `Access.seesOthers`, a `Access.visibleGroups` k tomu prida sekce, ktere smi filtrovat: kdo vidi celou firmu vsechny, vedouci jen ty, ktere vede. Nabidnout mu sekci, ze ktere stejne nic neuvidi, je jen matouci. ### Fronta umi prirazovat Nova `QuickAssign`: u kazdeho ticketu ve fronte tlacitko **Vzit si** a dve roletky, komu a do ktere sekce. Prirazeni je ve fronte hlavni prace, ne jedna z mnoha veci na detailu - otevirat kvuli tomu kazdy ticket znamena u dvaceti ticketech ctyricet kliknuti navic. Dve roletky, ne jedna: clovek a sekce jsou dve ruzna rozhodnuti. "Tohle je pro ucetni" jde rict bez toho, aby se resilo, kdo z nich ma dovolenou. Kdo nesmi rozdavat praci ostatnim, vidi jen "Vzit si". Endpointy uz existovaly (`/claim`, `/assign`, `/group`), nova je jen cesta k nim. ### Vychozi zalozka podle adresy Odkaz z widgetu nese `assignee`, takze `unassigned` otevre frontu a konkretni resitel nebo sekce pohled Vsechny. Bez toho by clovek prisel z dlazdice "fronta bez resitele" a koukal na svoje tickety. Kdo neni veden jako resitel, nema zalozku Moje. Vychozi stav ji ale predpoklada, protoze prava dorazi az po prvnim vykresleni - proto pojistka, ktera po nacteni prav prepne na prvni dostupnou zalozku. ### Hledani s tim pocita Zapsano do navrhu: hledani ma zapadnout do zalozky, ve ktere clovek stoji, ne ji obejit. Z Mych hleda ve svych, z Nezarazenych ve fronte, z Vsech v celem rozsahu stropu, a vysledek se vraci do te same zalozky. Volba "hledat vsude" dava smysl jen tam, kde zalozka Vsechny vubec je. ### Overeno na bezici instanci | Kdo | Zalozka Vsechny | Sekce ve filtru | | ------------------------- | --------------- | --------------- | | spravce platformy | ano | vsechny | | Vomacka, vede Servicedesk | ano | jen Servicedesk | | Kriz, radovy clen | ne | zadne | A cely pohyb ticketu frontou: admin preda TK-4819 do sekce, Kriz ho vidi ve fronte, vezme si ho a objevi se mu v Mych ticketech. ## 2026-09-02 - Nezarazene vidi kazdy, a dlazdice "Moje tickety" Dve veci, ktere ze stropu viditelnosti vypadly. ### Nezarazeny ticket je ve stropu vzdycky Prvni verze ho schovavala: kdo nevidel na celou firmu, nevidel ticket, ktery nema resitele ani skupinu. Byla to diera v provozu. Prichozi ticket, ktery jeste nikdo nesmeroval, nepatri do zadne sekce, takze by ho nevidel nikdo krome vedeni - a nikdo by si ho nevzal. **Fronta je spolecna, prave proto je to fronta.** `withinVisibility` proto pousti kazdy ticket bez resitele, at uz ma skupinu nebo ne. Jakmile si ho nekdo vezme, plati strop jako u kazdeho jineho. ### Dlazdice "Moje tickety" a "Fronta bez resitele" Byly v navrhu od zacatku a nikdy se neudelaly. Presne jak navrh rikal, staci zaznam v katalogu a zdroj v `builtinSources` - **zadna nova komponenta**, data pocita tatáz cesta jako u vykonu resitelu. ```ts 'list.myTickets': { kind: 'ticketList', filter: { assignee: ['me'], closed: false }, limit: 8 }, 'list.unassigned': { kind: 'ticketList', filter: { assignee: ['unassigned'], closed: false }, limit: 8 }, ``` K tomu tri veci, ktere si to vyzadalo: - **`closed` ve `WidgetTicketFilter`.** Bez nej se vyrizene tickety nedaly odfiltrovat: stav je volny retezec a vyjmenovat vsechny podoby slova "hotovo" se neda. Prospeje to i vlastnim widgetum, dosud neslo postavit "otevrene tickety podle typu". - **`ResolvedScope.personId` se plni vzdycky**, ne jen u pohledu `mine`. Prehled se pta v pohledu `tenant`, takze filtr `me` nemel co dosadit a dlazdice vracela prazdno. Kdo je to "ja", na pohledu nezalezi. - **Kdo neni veden jako resitel, tomu se "Moje tickety" nenabidnou.** Ticket se prirazuje resiteli, ne uctu, takze by takovy clovek koukal na prazdno navzdy a nedozvedel se proc. ### Vychozi rozlozeni Nahore to, co clovek muze udelat, teprve pod tim cisla: ```text Moje tickety, Fronta bez resitele Aktivni automatizace, Otevrene tickety, Bezici incidenty Posledni tickety, Incidenty ``` Graf behu z vychozi sady ven. Je to nejmene srozumitelna dlazdice pro noveho cloveka - behy ceho a co s tim - a zabira celou sirku. V katalogu zustava. **Zmena se projevi jen tem, kdo si dashboard jeste neupravili.** Rozlozeni se uklada za dvojici uzivatel a firma, kdo uz si ho osahal, musi dlazdice pridat rucne nebo dat "Vychozi". ### Overeno na bezici instanci Kriz jako radovy clen sekce: "Moje tickety" vraci jeho TK-4820, "Fronta bez resitele" nezarazeny TK-4819, a v seznamu ticketu vidi oba. Pred opravou `personId` vracela prvni dlazdice prazdno. ## 2026-09-02 - Detail ticketu ma rozradovace, ne tri karty pod sebou Navrh v [25](25-navrh-pristupny-portal.md) mel detail jako jednu slozku s rozradovaci, ale v aplikaci zustaly tri karty pod sebou: obsah, prichozi udalosti a log prubehu. Detail byl tri obrazovky dlouhy a to podstatne, tedy obsah pozadavku, se v tom ztracelo. Ted jsou to listy jedne karty: | Rozradovac | Co je na nem | | ------------ | ------------------------------------------------- | | Požadavek | obsah a pole na komentar, vychozi | | Log průběhu | co delala automatizace a co ji sluzby vratily | | Události | co presne prislo zvenku | | Vlastní pole | hodnoty poli typu ticketu | U kazdeho je pocet, takze je videt, jestli se tam vubec vyplati kliknout. **Vlastni pole se nenabizeji, kdyz zadna nejsou** - prazdny rozradovac je jen dalsi vec k prehlednuti. Vychozi je vzdycky Pozadavek: to je duvod, proc clovek ticket otevrel. Log a udalosti jsou hloubkova data, na ta se sahá, az kdyz neco nesedi. Vlastni pole se do te doby nedala zobrazit vubec, i kdyz je ticket nesl - to je ten ctvrty rozradovac. Kresli je tatáz tabulka jako obsah ticketu (`DataTable` v `TicketBody.tsx`), takze se struktura vsude vypisuje stejne. Rozradovace pouzivaji **tentyz vzhled zalozek jako zbytek portalu**, ne tvar slozky z navrhu. Druhy styl zalozek na jedne obrazovce je presne ten drift, ktery jsme uklizeli u vstupnich poli. ## 2026-09-02 - Hierarchie firmy: kdo co vidi, a zalozka Firma Pohled na celou firmu dostal kazdy, kdo do ni patril: `scopes.push('tenant')` se v `access.ts` neptalo na nic. `mine` byl dobrovolny filtr, ne strop, takze resitel s roli `agent` poslal `?scope=tenant` a dostal cely provoz. Rozdil, na kterem to ted stoji: **pohled je co chci videt, strop je co vubec smim videt.** Existoval jen pohled a klientovi se veril. ### Model Priznak visi na **clenstvi**, ne na cloveku a ne na skupine. Diky tomu muze byt clovek v peti sekcich a jen ve dvou z nich videt vsechno - to role rict neumi, ta je jedna na celou firmu (`Membership` je dvojice firma a role). - `Membership.seesAllTenant` - vidi cely provoz firmy. Postaveni uctu ve firme, ne vlastnost resitele: clovek muze byt ve dvou firmach jednou reditel a jednou brigadnik. - `PersonGroup.members` je `{ personId, seesAll }` misto holeho `personIds`. `seesAll` je vedouci sekce. Vedoucich muze byt vic a jeden clovek muze vest vic sekci, proto to neni zvlastni pole na skupine. - Stara podoba `personIds` se dal cte, prevadi ji `groupMembers` - jedine misto, kde se to deje, aby skupina ulozena driv neprisla o cleny. ### Vypocet stropu `visibilityFor` v `data/access.ts`, sjednoceni ne prunik: ```text spravce platformy nebo seesAllTenant -> cela firma jinak -> moje tickety + vse ze sekci, kde mam zaskrtnuto + fronta techhle sekci ``` `seesAllTenant === undefined` jsou zaznamy ulozene driv. Tam rozhoduje pravo `ticket.assign.others`: kdo dosud smel prehazovat cizi praci, uz stejne cely provoz videl, takze se mu nic nebere. Bezny resitel timhle sitem neprojde a spadne na svoje sekce, coz je prave ta zmena, o kterou jde. ### Kde se to vynucuje **Strop je povinna soucast `TicketFilter`**, stejne jako `tenantIds`. Nepovinny filtr na prava je filtr, ktery jednou nekde chybi - takhle prekladac ukaze kazde misto, ktere ho jeste nema. Pri zavedeni jich naslo trinact. - `listTickets` filtruje pres `withinVisibility` - `getWorkload` a `getAgentStats` uz nesahaji do pole ticketu primo, jdou pres `listTickets`. Driv obchazely kazde omezeni viditelnosti - `getTicket` kontroluje strop i u jednoho ticketu. Bez toho by stacilo znat ID: seznam by ho schoval, ale adresa detailu vydala - detail a prevzeti pocitaji strop **za firmu ticketu**, ne za prave prepnutou - odkaz z pohledu "vse" muze vest do jine firmy uzivatele ### Helpdesk Ridi se toutez hierarchii, jen "moje" znamena neco jineho: zadavatel pozadavek nikdy nema prirazeny, resi ho nekdo u dodavatele. Proto ma ticket nove `createdById` a v helpdesku plati: kdo vidi celou firmu, vidi vsechny jeji pozadavky, ostatni jen ty svoje. ### Zalozka Firma Bylo to roztazene mezi dve mista. Nove je pod **Firma** vsechno, co se firmy tyka: Prehled, Resitele, Sekce, Role a prava, Typy ticketu, Pozvanky. V Nastaveni zustal Muj ucet a platformni veci, ktere klient nevidi. Role a Typy jsou ted samostatne komponenty (`RolesAdmin`, `TicketTypesAdmin`), ne kus stranky nastaveni. Sekce maji vlastni panel misto obecneho `EntityAdmin`: u kazdeho clenstvi je prepinac "vidi vse ze sekce" a radek na cloveka obecny editor s poli neumi. ### Overeno na bezici instanci Tri ucty nad tymiz daty: | Kdo | Vidi | | -------------------------- | --------------------------------------- | | spravce platformy | vsechny 3 tickety vcetne neprirazeneho | | Vomacka, vede Servicedesk | 2, tedy tickety obou clenu sekce | | Kriz, radovy clen tehoz | 1, jen svuj | K tomu: adresa cizho ticketu vraci 404, vytizeni tymu ukazuje jen viditelne, a po zaskrtnuti priznaku u clenstvi se rozsah zmeni hned. ### Co z toho plyne Neprirazeny ticket **bez skupiny** nevidi nikdo krome toho, kdo vidi celou firmu. Je to spravne podle modelu - takovy ticket nepatri do zadne sekce - ale znamena to, ze prichozi praci musi nekdo smerovat, jinak radovym clenum nikdy nedorazi. ## 2026-09-02 - Obsah ticketu se na detailu cte `body` je jeden retezec, ale co v nem stoji, urcuje ten, kdo ho naplnil. Krok "Zalozit nebo doplnit ticket" dosadi `{{data}}` a sablona objekt prevede na JSON, takze v obsahu skoncila slozena zavorka vypsana jako odstavec. Necitelne, i kdyz jsou v ni presne ty udaje, kvuli kterym ticket vznikl. Nova `web/src/components/dashboard/TicketBody.tsx` telo prectе: | Co v tele je | Jak se to ukaze | | ----------------------- | ------------------------------ | | veta od cloveka | text tak, jak prisel | | cely retezec je JSON | tabulka klic a hodnota | | seznam objektu | tabulka se sloupci podle klicu | | text a za nim JSON | odstavec a pod nim tabulka | | cokoliv, co se neprecte | text tak, jak prisel | Ctvrta radka je bezny pripad: sablona `{{voicebotId}}, {{data}}` da presne tohle. Deli se az od **prvni zavorky, od ktere je zbytek platny JSON** - bez te druhe podminky by veta s jednou slozenou zavorkou skoncila rozpulena. Zanoreni se kresli o uroven niz jako vnorena tabulka, hloubeji uz jako JSON: tabulka v tabulce v tabulce se necte a odesilatele umi zanorit data hluboko. Nic se nezahazuje, neprecteny obsah se ukaze jako puvodni text. Overeno na osmi tvarech tela vcetne toho, ktery na instanci opravdu vznika, vety se slozenou zavorkou a nedopsaneho JSONu. ## 2026-09-02 - U posledniho volani je videt, co presne prislo Prvni verze seznamu poslednich volani ukazovala cas a duvod odmitnuti, ale ne data. To je k nicemu: "parametr data ma mit typ text" nerekne, co tedy prislo, a bez toho zbyva hadat. Argument, ze se telo nema drzet kvuli zakaznickym datum, navic neobstal - tickety si cela prijata tela u udalosti drzi uz davno, takze si to jedno misto zakazovalo neco, co aplikace jinde bezne dela. Radek volani se ted rozklikne na dve veci: - **rozpad po parametrech**: jmeno, cesta, co se cekalo a co na te ceste opravdu bylo, vcetne nahledu hodnoty. Sklada ho `readPayload`, protoze jen tam je videt kontrakt i telo naraz - pozdeji uz se kontrakt muze zmenit - **cele prijate telo**, vcetne klicu, ktere odesilatel posila navic a kontrakt o nich nevi. Delsi nez 8 kB se usekne ```text ok callSid | cekame string, povinny | prislo text = CA-B CHYBA status | cekame string, povinny | prislo nepřišlo CHYBA data | cekame object | prislo text = tohle je text misto objektu ``` Telo se do zaznamu zmrazi na retezec, aby se pozdeji nezmenilo pod rukama, a serializace je v `try` - cyklicka struktura nesmi shodit prijem webhooku. ## 2026-09-02 - Kontrakt webhooku: objekt jde vybrat a odmitnuti je videt Parametr spoustece `data` byl deklarovany jako `type: string, required: true`, zatimco odesilatel ho posila jako objekt a v prvni zprave hovoru ho jeste nema. Kazde volani proto skoncilo na 400 a automatizace hodinu nedelala nic. Za tim byly tri veci, kazda sama o sobe malicherna: - **Rucne pridany parametr byl vychozi povinny**, zatimco parametr odvozeny z ukazkoveho tela nepovinny. Dve ruzna vychozi nastaveni pro tutez vec v jednom formulari. Nove je nepovinny i rucne pridany: povinny znamena "odmitni volani" a do toho nema nikdo spadnout omylem. - **Objekt a seznam neslo vybrat.** `declarableFieldTypes` nabizel jen `string`, `number`, `boolean` a `date`, a `TriggerConfig.tsx` mel jeste treti kopii toho seznamu. Deklarovat `data` jako objekt tedy neslo, i kdyz `matchesType` objekt umi a `operatorsByType` pro nej ma operatory. Ted jsou v nabidce oba a klient si vlastni kopii nedrzi. - **Odmitnuti nebylo nikde videt.** Skoncilo jako `console.warn` v logu kontejneru: zadna udalost, zadny beh, nic na detailu automatizace. Ten treti bod je ten podstatny. Chybu v kontraktu udela ten, kdo ho psal, ale **400 dostane odesilatel** - a ten s tim nic nenadela, casto je to cizi sluzba, ktera volani neopakuje. Majitel automatizace se nedozvi nic a v portalu vypada vsechno v poradku. Detail automatizace proto ukazuje **poslednich deset volani**: cas, jestli proslo nebo ne, a u odmitnutych duvod. Telo se schvalne neuklada, duvod uz rika, co je spatne, a drzet payloady by znamenalo mit v pameti kopie zakaznickych dat. Seznam je v pameti, restart ho zahodi - je to diagnostika posledni hodiny, ne historie. Incident se z toho nezaklada a upozorneni se neposila: staci radek, protoze implementator se ozve sam a tady je videt co. Vzorova automatizace ma `data` opravene na `type: 'object', required: false`. Overeno na bezici instanci s vlastnim DATA_DIR: telo s `data` jako objektem projde, prvni zprava hovoru s `data: null` projde, telo bez `callSid` se dal odmita - a vsechna tri jsou videt v seznamu poslednich volani. ## 2026-09-02 - Navrh: pristupny portal a viditelnost Novy [25-navrh-pristupny-portal.md](25-navrh-pristupny-portal.md). Je to navrh, ne popis stavu, nic z nej zatim neni naprogramovane. Vzniklo to z otazky, jak portal priblizit cloveku, ktery ho nikdy nevidel. Odpoved se rozpadla na pet veci, ktere spolu souvisi vic, nez to vypada: - **prehled ukazuje jen cisla, zadna slovesa.** Vsech sest widgetu ve vychozi sade jsou statistiky a seznamy. Navrh pridava "Moje tickety", "Fronta bez resitele", "Co potrebujete udelat" a "Zaciname" - **formulare nemaji spolecnou vrstvu.** `inputClass` je nadefinovany na 13 mistech a rozesel se do peti ruznych vzhledu, `Field` je napsany trikrat. V `components/ui/` neni zadny formularovy prvek - **hledani neumi to jedine, k cemu je.** Klientsky filtr nehleda v obsahu, ve vlastnich polich ani v externim ID, takze hovor podle `callSid` se dohledat neda. Navrh je modal s kriterii, protoze ticket nema pevnou sadu poli - **viditelnost ticketu se neda omezit.** Pohled `tenant` dostane kazdy, kdo do firmy patri, `mine` je dobrovolny filtr a ne strop. Navrh vede viditelnost pres clenstvi (priznak na firme, priznak u kazde skupiny), ne pres role - role jsou na celou firmu a neumi rict "v jedne sekci vidim vse, v druhe svoje" - **uloziste neprezije nasazeni.** Overeno v praxi: po dnesnim nasazeni zustaly ve firme dva tickety, predtim jich byly tisice Dve veci, ktere stoji za zapamatovani, i kdyby se navrh nikdy nedodelal: 1. **Strop viditelnosti nepatri do hledani, ale do cteni ticketu.** Cesty k ticketum jsou dnes tri (`listTickets`, `getWorkload`, `getAgentStats`) a dve z nich filtruji `tickets` primo. Kdyby strop resilo jen hledani, obejde se widgetem nebo souhrnem. 2. **Pohled a strop nejsou totez.** Pohled je co chci videt, strop je co vubec smim videt. Dnes existuje jen pohled a klientovi se veri. Ctyri otevrene otazky jsou v zaveru navrhu: co znamena "moje" pro strop, jak se strop potka s helpdeskem, jestli hledat i v udalostech a kdo smi viditelnost nastavovat. ## 2026-09-02 - Vzorova automatizace srovnana s bezici instanci `seedRealAutomations` v `src/data/automationStore.ts` drzelo starsi podobu stromu nez ta, ktera na instanci opravdu bezi. Protoze data neprezivaji redeploy, je tenhle seed jedine misto, kde nastaveni prezije nasazeni - a kdyz se rozejde, znamena to po kazdem nasazeni stavet strom rucne znovu. Opsano z bezici instance: spoustec ma sest parametru misto tri (pribylo `result`, `rating` a `data`, vsechny s cestou do `data`), krok "Zalozit nebo doplnit ticket" pise do obsahu `{{voicebotId}}, {{data}}` a stav bere z `{{result}}`, a za nim je podminka nad vysledkem: cokoliv krome "Chybějící informace" ticket zavre, jinak jde na servicedesk s vysokou prioritou. Pravidlo, ktere z toho plyne a je i v komentari u funkce: **kdyz se strom na instanci zmeni, patri ta zmena sem.** Jinak ji dalsi nasazeni zahodi. ## 2026-09-02 - Zalozit NEBO DOPLNIT ticket: doplneni konecne doplnuje Automatizace mela v kroku "Zalozit nebo doplnit ticket" pole Obsah nastavene na `{{rating}}`. Data v behu prokazatelne byla - v udalostech ticketu je hodnoceni videt cele - ale ticket zustal s prazdnym obsahem. ### Cim to bylo `intakeEvent` deli praci na **zalozeni** a **navazani na existujici ticket**. Vsechno z `create` platilo jen pro tu prvni vetev. U existujiciho ticketu se doplnovaly pouze vlastni pole a stitky. Predmet, obsah, typ, priorita, kanal, zakaznik, resitel ani skupina ne - tise se zahodily. U hovoru to znamena, ze obsah nedorazi nikdy. Prvni zprava jen oznami, ze hovor zacal (`status: in-progress`, `data: null`), a **prave ta ticket zaklada**, tedy s prazdnym obsahem. Hodnoceni prijde az posledni zpravou, kdy uz ticket existuje. Stav byl jedina vyjimka, protoze ho krok nastavuje zvlast pres `updateTicketStatus`. Proto fungoval a zbytek ne, a proto to vypadalo jako chyba jednoho pole. ### Jedno pravidlo misto dvou seznamu Zalozeni a doplneni ted delaji totez: > Neprazdna hodnota prepise, prazdna nemaze. `IntakeInput` ma na to `apply`, v `create` zustal jen zaloha predmetu a vychozi stav. Dva ruzne seznamy poli by se stejne zase rozesly a nekde by zas neco chybelo. Prazdna hodnota nemaze schvalne. Prave to byla puvodni obava, kvuli ktere se zapisovalo jen pri zalozeni: pozdejsi zprava bez jmena zakaznika je bezna a smazat kvuli ni jmeno by bylo horsi nez ho nedoplnit. Nove se prepise jen to, co odesilatel opravdu poslal, takze pojistka plati a data se neztraci. Dve vyjimky zustavaji: zaloha predmetu z externiho ID plati jen pri vzniku a stav chodi pres `updateTicketStatus`, ktere resi i priznak vyrizeni, cas vyreseni a pocet znovuotevreni. ### Data smi chodit po castech Vlastni pole typu se **scitaji podle klicu**. Prvni zprava posle `data`, druha `data2` a ticket ma obe. Odesilatel se nemusi predem dohodnout, co vsechno posle, a nemusi posilat vsechno pokazde. Prazdny retezec pritom pole nemaze. Sablona, ktera na nic neukazuje, se dosadi prazdnem, takze `{"vysledek":"{{result}}"}` u zpravy bez vysledku posilalo prazdno a **prepsalo tim hodnotu z minule zpravy**. Vymazat pole jde poslanim `null`: to uz je zamer, ne vedlejsi ucinek nevyplnene sablony. ### Stav se ted ulozi uz pri vzniku `create.status` se do `createTicket` vubec nepredaval, takze ticket vznikl s vychozim "Nový" a hned se prepsal. V logu pak stalo "stav Nový -> completed" u ticketu, ktery v "Nový" nikdy nebyl. Komentar u volajiciho tvrdil, ze uz je to opravene - opravena byla jen jedna strana. ### Faze dorazena do konce Faze byla zrusena uz driv, hodnoty z ni patri do stavu. V katalogu po ni ale zbyval krok "Posunout do dalsi faze" a pole Faze u zalozeni ticketu. Ani jedno nemelo co delat: ticket ani typ ticketu fazi nemaji. Krok by pri behu selhal na chybejicim skriptu a pole se tise zahazovalo, takze `{{status}}` napsany do Faze nedelal nic. Oboji je pryc. ### Aby bylo videt, ze se to ulozilo Krok v logu rekne, co doplnil: `doplnen TK-123, stav completed, obsah`. Driv radek jen oznamil, ze se ticket doplnil, a nebylo poznat cim - u dat, ktera dorazi az druhou zpravou, je to zrovna ta informace, kterou clovek hleda. ## 2026-08-28 - pad portalu uz nesmi shodit stranku a zaklada incident Ukazka tela webhooku shazovala cely builder pri psani cesty parametru. Chyba sama byla na jednom radku, ale to podstatne je, **ze jedna vyjimka pri vykreslovani odstranila celou stranku**. Uzivateli zustala bila plocha a rozdelana prace byla pryc. Aplikace na to nemela zadnou pojistku. ### Pojistka proti padu vykreslovani Nova `web/src/components/ErrorBoundary.tsx` obaluje obsah portalu. Mistni chyba ted shodi svoji cast obrazovky, ne aplikaci: navigace, prepinac firmy i odhlaseni zustanou funkcni a uzivatel ma kam odejit. Odchod na jinou stranku pojistku srovna zpatky. Je to jedina trida v celem klientovi. Hook na tohle neni a nebude, React to umi zachytit jen takhle. ### Pad zaklada incident `POST /api/dashboard/client-crash`. Bez toho je jedina stopa v konzoli prohlizece uzivatele, kam se nikdo nedostane - takze bychom o padu vedeli jen tehdy, kdyby ho nekdo nahlasil. To znamena o vetsine padu nevedet. Incident nese dve casti, stejne jako ostatni incidenty: - `title` a `impact` cte zakaznik, tedy zadne stack trace, - `detail` cte spravce platformy: hlaska, misto v kodu, strom komponent, adresa stranky, ucet, prohlizec a **verze buildu**. Verze buildu je tam schvalne. U tohohle padu se ukazalo, ze bez ni se neda poznat, jestli uzivatel vidi chybu, ktera uz je opravena, nebo novou. Tentyz pad na tomtez miste zalozi incident nejvys jednou za deset minut. Pad pri vykreslovani se opakuje pri kazdem prekresleni a jinak by z jedne chyby vzniklo padesat incidentu a ten pravy by v nich zapadl. ### Sama ukazka - Do cesty parametru se da zanorit vzdy. Kdyz na miste je skalar, nahradi se schrankou - driv se zapisovalo do retezce a to shodilo stranku. - Parametr, kterym vede cesta jineho parametru, uz nedostane zastupnou hodnotu podle typu. Plati vnitrni struktura: kdyz je jeden parametr `data` a druhy `data.result`, **`data` musi byt objekt**. Zastupna hodnota by ho prepsala. - Cela ukazka je navic v `try`. Je to napoveda a nesmi shodit ani ten kus obrazovky, at uz do ni prijde cokoliv - parametry se pisou znak po znaku a rozdelany stav je normalni. ## 2026-08-28 - MCP EasyWeb podle skutecne specifikace (auth v2) Predchozi verze posilala na `/login` jen jmeno, heslo a nazev zarizeni. Server na to odpovidal `400 Bad Request` na cokoliv, i na spravne udaje, protoze **cekal neco uplne jineho**. EasyWeb ma auth v2: token se nevydava proti uctu, ale proti **zarizeni**, a to je pár klicu ECDSA P-256. Jmeno a heslo se pouziji jedinkrat, kdyz se klic registruje, a soucasti registrace je podpis, kterym zarizeni dokazuje, ze privatni klic k poslanemu verejnemu opravdu ma. Od te chvile se podepisuje kazde volani, ktere s tokeny hybe. Overeno proti bezicimu serveru: se spravnym telem uz `/login` nevraci 400, ale 401 s neplatnymi udaji. Ucty z jejich testovaciho `settings.json` na verejnych instancich neplati, takze dal se bez skutecnych udaju nedostanu. ### Prihlaseni - `src/mcp/easyweb/crypto.ts` - klice, podpisy, otisky. Podpis musi byt **P1363**, tedy holé `r || s`, 64 bajtu. Node podepisuje ve vychozim nastaveni do DER a ten by protistrana neuznala. - `src/mcp/easyweb/device.ts` - klic zarizeni se vyrobi jednou a **prezije restart**: uklada se mezi udaje konektoru, ktere uz jsou zasifrovane. Pole ma novy priznak `managed`, takze ho ve formulari nikdo nevidi. - `src/mcp/easyweb/session.ts` - tri tokeny, retez s ustupy (platny pristupovy, obnova obnovovacim, obnova zarizenim, cele prihlaseni), **jedno prihlaseni naraz na konektor**, tokeny **jen v pameti**. Ta posledni tri pravidla nejsou opatrnost navic: tokeny jsou jednorazove, druhe pouziti server odmita kodem 409 a **umi zarizeni zablokovat**. Dve soubezne automatizace nad tymz napojenim by bez jedne sdilene rozdelane operace spustily dve obnovy a druha by pracovala se spotrebovanym tokenem. ### Transport - **Server si sam vybira, jestli odpovi JSON telem, nebo SSE streamem**, a streamem odpovida i na obycejna volani. Klient proto nabizi obojí a cte stream **po kouscich** - u dlouhych uloh ho server sam nezavira a posila do nej tlukot srdce, takze cekani na konec by skoncilo az timeoutem. - Handshake plati **na token**, ne na volani. Server drzi sezeni u tokenu. - Odmitnute sezeni prijde jako chyba `-32008` **uvnitr uspesne odpovedi**. Bez zvlastniho osetreni by to vypadalo jako chyba volani a krok by skoncil misto toho, aby se prihlasil znovu. - Seznamy se skladaji pres vsechny stranky. Bez toho je videt jen prvni, tedy asi dvacet nastroju, a vypada to jako uplny seznam. - Odpoved se rozbaluje rekurzivne (`structuredContent`, `contents`, `content`, JSON zapsany jako text). Jinak by v kroku skoncil JSON jako retezec, se kterym uz se v podmince nic nesvede. ### Dlouho bezici nastroje Nastroj, ktery server odmitne spustit rovnou, se spusti jako **uloha** a ceka se na ni dotazovanim. Stav z `tasks/list` a `tasks/get` je to jedine, co je vzdycky pravda - notifikace o prubehu chodi nejvys jednou. Dokoncena uloha ze seznamu mizi, takze po nekolika marnych kolech se prejde na primy dotaz. Limit kroku se pri tom posouva z patnacti sekund na deset minut. Kdyz uloha nedobehne ani tak, neni to chyba: na serveru bezi dal a krok to rekne. ### Co z toho zbylo nedodelane Trvaly kanal notifikaci (GET SSE), nahravani souboru po castech a hlidani zmen kontraktu podle verze serveru. Vsechno je v [24-mcp-konektory.md](24-mcp-konektory.md) v seznamu toho, co chybi. ## 2026-08-28 - dve MCP sluzby: obecna a EasyWeb, strankovani nastroju MCP je standard, ale **prihlaseni k nemu ne**. Oficialni specifikace stoji na OAuth 2.1 a objevovani autorizacniho serveru pres `.well-known`. EasyWeb (Centaur) ma prihlaseni vlastni: `POST /login` s HTTP Basic vrati trojici tokenu a obnovuje se vlastnimi endpointy. Zadny OAuth, zadne `.well-known`, jina verze protokolu, zadne SSE ani hlavicka sezeni. Proto jsou v katalogu **dve sluzby**, ne jedna s prepinacem: firma pri zakladani konektoru vyplnuje neco jineho. U obecne ID a tajemstvi aplikace nebo hotovy token, u EasyWebu jmeno, heslo a nazev zarizeni. Slucovat to by znamenalo formular, kde je pulka poli vzdycky k nicemu, a hadani, ktera pulka to je. Obecna sluzba pritom **zustava plnohodnotna**. Vlastni server je duvod pridat sluzbu, ne duvod zavrit dvere ostatnim. ### Pribylo - `src/mcp/dialect.ts` - rozdily obou serveru na jednom miste: prihlaseni, verze protokolu, jestli se prijima SSE a jestli se posila `Mcp-Session-Id`. Rozesete po klientovi by u kazdeho dalsiho serveru pribyl dalsi `if` jinde. - **Sluzba MCP EasyWeb.** Adresa, jmeno, heslo, nazev a otisk zarizeni. Prihlasovaci adresy si portal odvodi z adresy serveru sam. Otisk se doplnuje z ID konektoru, aby server poznal totez zarizeni. - **Hotovy token u obecne sluzby.** Rada verejnych serveru nic jineho nenabizi a bez toho by na ne neslo zalozit konektor. - **Objevovani pres `WWW-Authenticate`.** Specifikace to ma jako povinnou cestu: server u odpovedi 401 rekne, kde jsou jeho metadata. Pouziva se az kdyz obvykla mista selzou, protoze to stoji volani navic. - **Zivotnost z tela tokenu.** Kdyz server `expires_in` ani datum neposle, cte se `exp` z JWT. To je presne pripad EasyWebu. - **Strankovani nastroju.** Nastroj s parametrem `cursor` dostane v builderu prepinac Nacist vsechny stranky. Kurzor je hodnota z odpovedi, takze v dobe stavby stromu ho nikdo nezna a nejde ho vyplnit dopredu. Krok pak vraci navic `items`, `pages`, `pageCount` a `truncated`. Strop je 20 stranek. ### Opraveno Prihlaseni driv zkousela `password` grant a HTTP Basic proti hlavnimu endpointu. Prvni OAuth 2.1 zrusil, druhe neni nikde ve specifikaci a u EasyWebu by stejne neproslo - ten chce Basic na `/login`, ne na `/mcp`. ## 2026-08-28 - MCP: prihlaseni jmenem a heslem, tokeny si resi portal Predchozi verze chtela po uzivateli token. To bylo spatne zadani: zakaznik dostane ke svemu serveru **adresu, jmeno a heslo**, token nedostane a nema jak ho ziskat - vyda ho az autorizacni server a ma omezenou zivotnost. Novy `src/mcp/auth.ts` resi cely zivotni cyklus: - **Kde se prihlasit** se zjisti od serveru pres `/.well-known/oauth-protected-resource` a metadata autorizacniho serveru. Rucni pole na adresu je jen zaloha pro servery, ktere metadata nemaji. - **Cim** se zkusi v poradi `client_credentials` a `password`. Ktere z toho firma dostala, se z udaju samych poznat neda a nutit ji to vybirat by znamenalo ptat se na neco, co nevi. - **Bez autorizacniho serveru** se posle HTTP Basic. Mensi servery zadny OAuth nemaji a jmeno s heslem je u nich presne tohle. - **Zivotnost urcuje server.** Token se vymeni minutu pred vyprsenim, obnovi se pres `refresh_token`, kdyz ho server vydal, a kdyz `expires_in` chybi, pocita se s peti minutami - tedy odhaduje se dolu. - **Odmitnuty token** (401 na token, ktery jsme meli za platny) se zahodi a volani se zopakuje jednou. Podruhe uz ne, to uz nejsou platne udaje. Token se drzi **jen v pameti**. Je kratkodoby, po restartu se o novy rekne znovu, a ulozit ho by znamenalo vsechna rizika ulozeni bez jakekoliv vyhody. Kes si drzi otisk udaju, takze zmena hesla ulozeny token zneplatni. Zpusob prihlaseni je videt v hlasce u konektoru: uzivatel vyplnil jmeno a heslo a ma vedet, jak s nimi portal nalozil, nez zacne hledat chybu jinde. ## 2026-08-28 - MCP konektory hotove Firma si zalozi napojeni na svuj MCP server, stiskne **Nacist nastroje** a jeho nastroje se objevi v builderu jako kroky. Popis v [24-mcp-konektory.md](24-mcp-konektory.md). Navrh byl predtim dvakrat prepsany a stoji za to rict proc. Prvni verze davala nastroje do vyberu kroku, ale popisovala MCP jako obycejny katalog vzdalenych procedur - tak nedava nic navic proti HTTP konektoru. Druha verze to prehnala opacnym smerem a chtela nastroje predavat jen modelu. Vysledne zadani je uprostred: **klientem jsme my**, nastroj vybira clovek v builderu, a prinos je v tom, ze napojeni na cizi system vznikne bez radky kodu u nas. ### Pribylo - **Sluzba `mcp`.** Jedina v katalogu, ktera nema zadne pevne operace - rekne je az server. Udaje jsou adresa serveru, token nebo klic v `X-API-Key`. - **`POST /connectors/:id/mcp/tools`.** Zepta se serveru na `tools/list` a ulozi vysledek. Je to zaroven overeni konektoru, proto tlacitko "Overit" u MCP neni. - **Klient protokolu** (`src/mcp/client.ts`): handshake, sezeni z hlavicky odpovedi, odpoved jako JSON i jako SSE stream, strankovani nastroju. - **Prevod schemat** (`src/mcp/schema.ts`). Ze schematu vzniknou pole kroku a zpatky se z vyplnenych retezcu udelaji argumenty ve spravnych typech. Ten druhy smer je ten podstatny: server ceka `{"limit": 10}`, ne `{"limit": "10"}`, a rada serveru na tom spadne az uvnitr nastroje. - **Nastroje u konektoru** (sloupec `mcp`, migrace `004`). Bez ulozeni by po restartu zmizely z katalogu kroky, ktere uzivatel uz ma ve stromech. - **Vnitrni krok `runMcpTool`.** Jedna obsluha pro vsechny nastroje vsech serveru - ktery to je, rika az ID operace `tool::`. ### Rozhodnuti, ktera stoji za zminku - **Nastroj patri firme, ne katalogu.** `serviceCatalog(tenantId)` bez firmy nevrati zadny nastroj. Zapomenuty argument tak znamena "nic", ne "vsechno" - stejne pravidlo jako u filtru na firmu. - **ID operace nese ID konektoru.** Firma muze mit dva servery a na obou nastroj `search`. - **Krok se neopakuje.** MCP nema idempotencni klic, druhy pokus po timeoutu by nastroj provedl podruhe. - **Chyba nemaze nastroje.** Vypadek serveru nesmi vymazat kroky z hotovych automatizaci. Prazdny seznam se naopak ulozi. - **Servery se pri startu neobvolavaji.** Jeden nedostupny by shodil katalog vsem. ### Opraveno mimochodem - `setStatus` v `connectors/postgres.ts` melo v `RETURNING` doslovny retezec `${COLUMNS}` misto dosazeni. V rezimu `file`, ve kterem bezi nasazeni, se to neprojevilo. - Dva odstavce v [12-sluzby-a-konektory.md](12-sluzby-a-konektory.md) byly dvakrat. ## 2026-08-28 - dokument pro programatory Novy [00-pro-programatory.md](00-pro-programatory.md): rozcestnik, model ctyr pojmu (sluzba, skript, konektor, krok), pravidla, ktera plati vsude, tri cesty vzniku operace, tri rezimy uloziste a seznam mist, ktera jsou krehka. Neni to kopie [03-architektura-a-mapa-kodu.md](03-architektura-a-mapa-kodu.md). Ta rika, kde co lezi, tenhle rika proc to tak je. ## 2026-08-28 - transformace maji svou kategorii, pribylo XML ### Odstraneno - **Sluzba AI zpracovani textu.** Delala totez co OpenAI, jen jinymi slovy. Dva zpusoby, jak udelat jednu vec, znamenaji jen otazku, ktery pouzit. Ukazkove stromy a stopy, ktere na ni odkazovaly, ted miri na `openai.chat`. ### Pridano - **Kategorie Transformace dat.** Driv byly mezi obecnymi sluzbami vedle webhooku a pauzy. Ve chvili, kdy jich je vic nez tri, je clovek hleda jako skupinu, ne mezi spoustecemi. - **Skript `transform.to-xml`.** Zapise objekt jako XML vcetne escapovani. Cizi systemy si XML porad rikaji casto (ISDOC, EDI, banky, statni sprava) a bez tohohle se sestavuje retezcem v HTTP kroku, kde se na escapovani zapomene pokazde. Klice, ktere nejsou platny nazev prvku, se prejmenuji a rekne se to nahlas. Klic zacinajici cislici dostane podtrzitko misto odriznuti - `2024` a `2025` by jinak byly totez a v dokumentu by se srazily. - **Navrh cele skupiny transformaci** v [13-transformace-dat.md](13-transformace-dat.md): formaty, pole a hodnoty, seznamy, text a kontrola. Vcetne toho, co v seznamu zamerne neni a proc. ## 2026-08-28 - AI zpracovani textu funguje Sluzba byla v katalogu bez pristupovych udaju a s `appId: 'ai-text'`, tedy mirila na aplikaci, ktera neexistuje. Konektor nemel co vyplnit a krok nemel co vykonat. ### Zmeneno - **Sluzba ma API klic a skutecnou adresu.** Stoji na `api.openai.com/v1` stejne jako OpenAI, overeni je `GET /models`. Model se bere z konektoru (`ctx.config.model`), aby se nevyplnoval u kazdeho kroku znovu. ### Pridano - **Tri skripty**: `ai-text.classify`, `ai-text.extract`, `ai-text.generate`. Proc to neni jedna sluzba s OpenAI: tam se posila volny dotaz a vraci volny text. Tady je uloha pevna a **vystup taky** - `category` a `confidence`, objekt s popsanymi poli, text s poctem slov. Prave na to jde ve strome navazat podminkou, na volny text ne. ### Zjisteno u toho - **Overeni konektoru projde i u sluzby, ktera neexistuje.** Proxy vraci na neznamou aplikaci `200` s prazdnym telem, a sluzba bez `verifyPath` se overuje prave pres `/health`. Konektor tedy rekne "Sluzba odpovida" i tam, kde nic nebezi. Tyka se to E-shopu, WhatsAppu, Facebooku, Instagramu, SMS a Slacku. Zapsane v [21-realne-sluzby.md](21-realne-sluzby.md), zatim neopravene. ## 2026-08-26 - jazyky, skutecny snimek v heru a opravene titulky ### Pridano - **Prepinani jazyku** (`web/src/i18n/`). Vybrany jazyk je jedna hodnota pro celou aplikaci, drzena mimo React - tentyz vzorec jako u vybrane firmy. Volba prezije obnoveni stranky a prepisuje ``. Anglicky slovnik je `Partial`: co v nem neni, spadne na cestinu, takze preklad jde doplnovat po castech misto jednoho velkeho skoku. Podrobnosti v [23-jazyky.md](23-jazyky.md). - **Prepinac jazyka** v hlavicce a v mobilnim menu. ### Zmeneno - **Snimek v heru vypada jako portal doopravdy.** Byl svetly a ukazoval obrazovku, ktera v produktu neexistuje. Ted ma tmavy podklad, tytez zalozky vcetne prepinace firmy, tytez sloupce tabulky i tytez odznaky stavu jako stranka Tickety. Kdyz se navstevnik prihlasi a uvidi neco jineho, prvni dojem z produktu je, ze web lhal. - **Jmeno znacky si sklada `usePageMeta`, ne stranky.** Bylo napsane natvrdo ve dvaceti ctyrech titulcich, takze prejmenovani produktu zmenilo `brand.ts` a titulky ne - hlavicka rikala nove jmeno a zalozka v prohlizeci stare. ### Opraveno - **Odkaz "Prohlednout si to" vedl mimo proxy.** Byl to ``, tedy koren domeny, ne `/apps//sluzby`. Ted je to `Link` z routeru jako vsude jinde. Byl to jediny takovy odkaz v celem webu. ## 2026-08-26 - na aplikaci je videt, ktery build to je Nasazena aplikace o dva commity pozadu funguje a vypada skoro stejne jako nova. Bez oznaceni buildu se to nepozna a hleda se chyba tam, kde zadna neni. ### Pridano - **Oznaceni buildu v pate postranniho menu**, pod tlacitkem Odhlasit se: verze, cas buildu a cislo commitu. Text jde oznacit jednim kliknutim, aby se dal poslat dal. - **Totez jako `` v hlavicce stranky.** V JS balicku uz to je, ale ten se musi stahnout a rozbalit. Meta znacka je videt na jeden `curl` bez prihlaseni - a prave to clovek potrebuje, kdyz zjistuje, co bezi na produkci. - Hodnoty dosazuje Vite pri buildu (`define` a plugin `build-stamp` ve `vite.config.ts`), takze neplati pro repozitar, ale pro ten konkretni balicek. ### Zmeneno - **`.dockerignore` uz nevylucuje `.git/`.** Build z nej cte cislo commitu; bez nej by se poznal jen cas buildu, ne co v nem je. Do vysledneho image se `.git` nedostane, ten se sklada z ciste zakladni image. ## 2026-08-26 - rebrand na WorkNuke Prevzeti vizualniho smeru z predlohy. Popis, jak znacka funguje v kodu, je v [22-znacka-a-design.md](22-znacka-a-design.md). ### Zmeneno - **Barevnost.** Akcent z cyanove na zelenou `#B7FF3C`, podklad z modrocerne na zelenocernou `#0F1411`. Web i portal stoji na tychz tokenech, takze se to projevilo naraz na obojim. - **Druhy akcent uz neni barva.** `accent-*` ukazuje do teze zelene rady jako `brand-*`. Klice zustaly, protoze je pouziva 341 mist; prepsat je vsechny naraz by byla zbytecne velka zmena. Totez u utility `text-gradient`, ktera uz negeneruje prechod, ale plnou zelenou. - **Stav "v poradku" je modrozeleny.** Akcent je zeleny, a dve zelene na jedne obrazovce, kazda o necem jinem, nikdo nerozlisi. - **Pismo.** Archivo na nadpisy, IBM Plex Sans na text, IBM Plex Mono na cisla a ID. Nadpisy maji tesny proklad, ktery Inter neumel bez trikareni. - **Znak.** Plocha bomba misto pismene v gradientnim ctverci. `LogoMark` je samostatny export, aby sel pouzit i uvnitr snimku v heru. - **Tlacitko.** Plna zelena misto prechodu ze dvou barev. Prave tim vystupuje z rady. - **Hero.** Prokladany nadtitulek misto odznaku, dvouslovne radky nadpisu, jedno primarni tlacitko plus textovy odkaz, ctyri slovesa misto tri odrazek a snimek portalu vybihajici z prave hrany. - **Navigace** je pri levem okraji a bez pilulky za aktivni polozkou. Jedina pilulka na liste je primarni akce. - **Nazev, claim a kontakt** v `brand.ts`. ### Opraveno u toho - **Zare v heru je pozadi sekce, ne vrstva pod ni.** Prekryv se zapornym z-indexem se schoval za podklad stranky a nebyl videt vubec. ### Co se zamerne nezmenilo Klice v prohlizeci (`automia.token`, `automia.tenant`), ID firmy `tnt_automia`, ID aplikace `csbot-prototype` a prihlasovaci e-maily v ukazkovych datech. Vypadaji jako jmeno, ale jsou to identifikatory. Duvody jsou v [22-znacka-a-design.md](22-znacka-a-design.md). ## 2026-08-26 - prepinac firmy: do sidebaru a prekresli obsah cely Dodelavka predchoziho bodu. Tvrzeni "prepinac plati pro cely system" nebylo pravdive: `useApiQuery` se na zmenu firmy zeptat umel, ale komponenty, ktere volaji `apiFetch` primo - `EntityAdmin`, a tim cele Nastaveni a zalozky Resitele a Skupiny v Lidech - o zmene nevedely a zustala na nich data predchozi firmy. ### Opraveno - **Obsah dashboardu ma `key` podle vybrane firmy**, takze prepnuti prestavi cely strom komponent. Zadny dotaz nemuze zustat stary, protoze zadna stara komponenta nezustane. Doplnovat zavislost do kazde komponenty zvlast je totez zapomenuti, jen o patro niz. ### Zmeneno - **Prepinac je v sidebaru nad navigaci**, ne v hlavicce. Vypada jako ovladaci prvek: ram, ikona firmy, sipka. Predchozi verze mela pruhledny ram a splyvala s popiskem - prepinac, ktery neni videt, je k nicemu. - **V hlavicce zustal jen nazev aktivni firmy.** Driv tam stalo natvrdo `tenants[0]`, takze po prepnuti ukazovala porad prvni firmu ze seznamu. - Kdo patri do jedne firmy, vidi misto prepinace jeji nazev. Vyber z jedne moznosti neni vyber. ## 2026-08-26 - prava plati za firmu, ne za cloveka `permissionsOf(user)` scitalo role pres vsechna clenstvi. Kdo byl spravce v jedne firme, jednal jako spravce ve vsech, kam patril. Uzivatel ve dvou firmach je vzacny, dusledek ne - to neni edge case, to je diera v pravech. ### Zmeneno - **Firma je povinny argument** u `permissionsOf`, `hasPermission` i `hasAnyPermission`. Zamerne povinny: kdyby byl nepovinny, prvni volani bez nej by tise vratilo vsechna prava. Prekladac tim rovnou nasel vsech ctrnact mist, ktera se ptaji. - **Kazde misto se pta za tu spravnou firmu.** Akce nad ticketem za firmu toho ticketu, uprava zaznamu za firmu toho zaznamu, zalozky a helpdesk za firmu, kterou ma clovek prepnutou. Kdo edituje zaznam jine firmy, musi mit pravo tam, ne tam, kde je zrovna prepnuty. - **Cizi firemni role se ignoruje**, i kdyby na ni clenstvi odkazovalo. Jinak by stacilo pripsat si ji do clenstvi v jine firme a prava by se prenesla. - **Cache prav ma v klici firmu.** Bez toho by prvni dotaz odpovedel i na druhou. - **`readScope` cte z jedne firmy, te prepnute**, ne ze vsech, kam clovek patri. V nastaveni se driv michaly typy ticketu a role napric firmami bez ohledu na vyber - stejna vada jako u prepinace, jen na jinem miste. ### Opraveno - **Systemove role se pri startu srovnaji s kodem** (`syncSystemRoles`). Vychozi sada se pouzije jen do prazdneho uloziste, takze nove pravo v katalogu se k uz bezici instalaci nikdy nedostalo - `ticket.create` a obe prava helpdesku by spravci firmy nikdy nenabehla a zalozka by se neobjevila. Meni se jen prava, jen u nasich roli, nazvu se to netyka. ## 2026-08-26 - prepinac firmy plati pro cely portal Prepinac byl stav uvnitr stranky Prehled. Prepnuti na LogiTrans zmenilo prehled a nic jineho: ostatni stranky volaly API bez `tenantId` a server sahnul po vychozi firme, takze v Lidech zustali lide Automie. To neni nepohodli, to jsou cizi data pod hlavickou jine firmy. ### Zmeneno - **Vybrana firma je jedna hodnota pro cely dashboard** (`web/src/lib/tenant.ts`), ne stav stranky. Prezije obnoveni stranky. - **`tenantId` doplnuje `apiFetch`**, ne jednotlive stranky. Kdyz si to mela pridavat kazda stranka sama, vetsina na to zapomnela - a zapomnetlivost byla prave ta chyba. Nedoplnuje se, kdyz cesta uz firmu nese (proklik z widgetu), u `scope=all` a mimo `/api/dashboard`. - **`useApiQuery` se pri prepnuti zepta znovu.** Bez toho by na strance zustala cisla predchozi firmy, dokud by ji nekdo neobnovil. - **Prepinac je v hlavicce nad obsahem.** Driv byl v Prehledu, ted plati viditelne pro vsechno. Nahradil i text, ktery ukazoval natvrdo prvni firmu ze seznamu bez ohledu na to, co bylo vybrane. - **Ulozena firma, do ktere uzivatel uz nepatri, spadne na vychozi.** Jinak by po odchodu z firmy koncil kazdy dotaz na 403. ### Zjisteno u toho - **Prava se scitaji pres vsechny firmy**, neprepocitavaji se podle vybrane. Kdo je spravce v jedne firme a resitel v druhe, ma po prepnuti porad prava spravce. Data se tim neprolomi, server je filtruje dal podle firmy, ale tlacitka ukazuji vic, nez by mela. Zapsane v [07-firmy-a-prava.md](07-firmy-a-prava.md), sekce Co chybi. ## 2026-08-26 - helpdesk: pozadavek, ktery vidi zadavatel i resitel Zakaznik nemel jak poslat pozadavek. Ticket pritom patri jedne firme, kdezto u helpdesku figuruji dve - ta, ktera se pta, a ta, ktera to resi. ### Pridano - **Pole `Ticket.helpdeskSourceId`** s firmou, ktera pozadavek poslala. Vlastnikem (`tenantId`) zustava ta, ktera ho **resi**. Zamerne tak: kdyby byl vlastnikem zadavatel, mel by resitel pozadavek jen jako cizi ticket a nemel by ho ve sve fronte, ve statistikach ani v prirazovani. Takhle je to na jeho strane obycejny ticket a nemuselo se sahnout na nic, co uz funguje. - **`Tenant.helpdeskProviderId`**, tedy komu firma posila pozadavky. Nastavuje spravce platformy v Nastaveni, Firmy. Kdo koho obsluhuje je obchodni vztah, ne volba klienta - kdyby si dodavatele vybiral uzivatel, poslal by pozadavek nekomu, s kym nema smlouvu. Bez vyplneneho dodavatele se pozadavek nezalozi a rekne se to nahlas. - **Sekce Helpdesk** (`/dashboard/helpdesk`) a router `/api/dashboard/helpdesk`: seznam vlastnich pozadavku, zalozeni, detail s prubehem a komentar. Zamerne to **neni druhy seznam ticketu** - chybi tu filtry, prirazovani i fronta, protoze zadavatele nezajima, kdo to ma u sebe. - **Prava `helpdesk.view` a `helpdesk.create`.** Prideluje je admin te firmy pres role, stejne jako u ostatnich prav. ### Zmeneno - **`listTickets` umi filtrovat podle zdroje** (`helpdeskSourceIds`). Vyplnene **nahrazuje** filtr podle vlastnika, protoze zadavatel vlastnikem neni. Bezny seznam ticketu se nezmenil. - **`getTicket` a `addComment` maji druhou cestu dovnitr.** Firma, ktera pozadavek poslala, ho smi cist a pripsat k nemu komentar, i kdyz ho nevlastni. Komentar je jedina zmena, kterou nad nim smi: stav, resitele a typ urcuje ten, kdo to resi. ## 2026-08-26 - portal prestal ukazovat provozni veci a ticket jde zalozit rucne Sada oprav podle toho, co v portalu drhlo. ### Zmeneno - **Hlasky o ulozisti a o odchozi IP adrese vidi jen spravce platformy.** Kam se uklada a z jake adresy volame ven resime my, ne zakaznik. "Data se ukladaji do souboru na serveru" na nej navic pusobi jako priznani, ze mu tu praci muzeme ztratit. Nemazou se, jen se schovavaji - nam poradi porad. Totez plati pro radek "volano z IP" v historii overeni. - **Typ ticketu se v automatizaci vybira ze seznamu.** Bylo to textove pole, do ktereho mel clovek opsat ID typu odjinud. Novy druh pole `lookup` je **ciselnik a volny text zaroven**: bud se vybere ze seznamu typu te firmy, nebo se hodnota dosadi z dat (`{{data.typ}}`). Typy ticketu jsou vlastnost firmy, takze nabidka chodi za tu, ve ktere clovek je. - **Stav ticketu je otevreny naseptavac, ne ciselnik.** Stav je volny retezec a vzdycky byl - ticket muze prijit z cizi aplikace s jejim vlastnim stavem. Vyber ze seznamu tomu odporoval. Ted je to textove pole s nabidkou toho, co firma uz pouziva (`GET /api/dashboard/tickets/statuses`), a nova hodnota projde stejne dobre. Zapisuje se az pri opusteni pole, ne po kazdem pismenu. - **"Kanal" se prejmenoval na "Odkud pozadavek prisel"** a rika o sobe, ze je nepovinny a slouzi jen k filtrovani a ikone v seznamu. - **Vstupni parametry u webhooku jsou oznacene jako nepovinne.** Webhook prijme cokoliv; rucne vypsany seznam parametru neni podminka, ale pohodli pro podminky - a vyplni se sam z vlepene ukazky tela. Prazdny seznam uz nehlasi, ze "bez nich nelze pridat podminku". ### Pridano - **Zalozeni ticketu rucne** (`POST /api/dashboard/tickets`, pravo `ticket.create`, tlacitko Novy ticket na strance Tickety). Dosud ticket vznikal jen z automatizace nebo z prichozi udalosti, takze pozadavek prijaty telefonem nemel jak do systemu. - **Zakaznik u ticketu je nepovinny.** Rucne zalozeny ticket je casto ukol, ne pozadavek od nekoho zvenku; povinna firma a kontakt by znamenaly, ze si je clovek vymysli. Ve formulari je zakaznik schovany pod odkazem. ### Co z teze davky jeste neni - **Sekce Helpdesk** pro zakaznika vcetne sdileni ticketu mezi admin firmou a tim, kdo ho zalozil. - **Prepinac firmy nad celym dashboardem.** Dnes je to stav uvnitr stranky Prehled, takze se prepnuti neprojevi v Lidech ani jinde - ostatni stranky volaji API bez `tenantId` a server pouzije vychozi firmu. Ma to doplnovat API vrstva na jednom miste, ne kazda stranka zvlast. ## 2026-08-26 - odesilani e-mailu ze schranky firmy Sluzba E-mail byla v katalogu jako popis: bez udaju, bez vykonne casti. Ted se odesila doopravdy, ze schranky, kterou si firma vyplni v konektoru. ### Pridano - **Sluzba E-mail pres SMTP.** V konektoru server, port, sifrovani, uzivatel, heslo, adresa a jmeno odesilatele a adresa pro odpovedi. Zadne udaje v prostredi - kazda firma odesila ze sve schranky. - **Vnitrni krok `email/send`.** SMTP neni HTTP a skript umi jen `ctx.http`, takze operaci vykonava vnitrni krok stejne jako zalozeni ticketu. Pristupove udaje pritom zustavaji v konektoru. - **Druh pole `html`.** Telo zpravy se pise jako HTML a builder ho vykresli jako vysoke pole s neproporcionalnim pismem. Runtime v nem **escapuje dosazene hodnoty**: znacky autora sablony jsou zamer, ostre zavorky v hodnote od zakaznika ne. Bez toho by text ticketu s `` prepsal rozvrzeni zpravy a `