# 99 - Zaznam zmen Nejnovejsi nahore. ## 2026-08-13 - stav ticketu je volny retezec, ciselnik pryc ### Zmeneno - **`Ticket.status` je volny retezec.** Ciselnik `new | open | waiting | resolved` je pryc. Tickety chodi z cizich aplikaci, ktere maji svoje stavy - voicebot posila `ringing` a `completed`, e-shop `pripraveno k expedici`. Nutit je do nasi ctverice znamenalo, ze u ticketu svitilo "Novy", i kdyz byl podle odesilatele davno hotovy. - **Pribyl priznak `Ticket.closed`.** Fronta, vytizeni i statistiky potrebuji vedet, co uz nikdo neresi, a z volneho retezce to poznat nejde. Nastavuje se sam: podle `TicketType.closedStatuses`, a kdyz je typ nema, podle bezneho pojmenovani (`vyreseno`, `hotovo`, `completed`, `closed`). - **`stage` zruseno.** Byla to obchazka, jak dostat cizi stavy do ticketu, aniz by se sahlo na ciselnik. Kdyz je stav volny, druhe pole na tutéz vec je jen zmateni. Hodnoty z faze patri do stavu. - Vyber stavu v detailu ticketu nabizi stavy typu, doporucene a **ten, ktery ticket ma prave ted** - jinak by hodnota z cizi aplikace ze seznamu zmizela a prvni rucni zmena by ji prepsala. - Filtr v seznamu ticketu nabizi **stavy, ktere v datech opravdu jsou**, ne pevnou ctverici. - Barva u stavu: zname nazvy maji svou, cokoliv jineho neutralni. Hadat, jestli je `ringing` dobre nebo spatne, by bylo horsi nez nehadat. ### Overeno 11 kontrol proti bezicimu serveru: ticket z voicebota ma stav `ringing`, po dalsich zpravach `in-progress` a `completed`, `completed` se pozna jako hotovo, hotovy ticket zmizi z fronty, filtr nabizi stavy z dat a funguje na ne, widget je ukazuje misto ciselniku a rucne jde nastavit i `ceka na zpetne volani`. ## 2026-08-13 - nastaveni prezije nasazeni, ukazkova data uz se nevraci Bez databaze lezi data uvnitr containeru, takze **redeploy je smaze** a seed je nasype znovu. Dokud nebude Postgres, resi se to takhle: ### Zmeneno - **Ukazkova data jen se `SEED_DEMO=1`.** Automatizace, tickety a incidenty se uz po kazdem nasazeni nevraci. Konfigurace (firmy, uzivatele, role, resitele, typy, widgety) se nasypava dal - bez ni je portal nepouzitelny. - **Token webhooku z `WEBHOOK_TOKEN_TEST`.** Driv se pri kazdem nasazeni vygeneroval novy, takze odesilatel musel prepisovat adresu. Ted je token v promenne aplikace: neni v gitu a adresa se nemeni. - **Automatizace, ktera na instanci opravdu bezi, je v seedu.** Je to provizorium, ne cil - az data prezijou nasazeni, patri zpatky do dat. ### Pridano - `ticket/upsert` umi **vsechna pole ticketu**: zakaznik (firma, kontakt, kam odpovidat), kanal, odkaz na zdroj, priorita, stitky, resitel, skupina a vlastni pole typu jako JSON. Zakaznik a kanal se vyplnuji jen pri zalozeni, aby pozdejsi udalost s prazdnym jmenem neprepsala, co uz tam je. - **Kroky ve strome jdou sbalit**, vychozi je sbaleno. Sbaleny krok ukazuje, co ma vyplneno, ne popis operace, ktery uzivatel zna. - Seskupovani widgetu **podle faze** a dva nove widgety: tickety podle stavu a podle faze za tento mesic. Obojí se proklikne na vyfiltrovany seznam. - Filtr na fazi v seznamu ticketu vcetne adresy. ### Opraveno - **Faze se ukazuje jako stav.** Kdyz ticket ma fazi z workflow sveho typu (napr. stav hovoru od voicebota), ukazuje se ona; nas zivotni cyklus zustava vedle jako drobny text, protoze se z nej pocitaji statistiky. Driv byl videt jen nas ctyrprvkovy ciselnik, coz u ticketu z cizi aplikace nedava smysl. ### Overeno 18 kontrol proti bezicimu serveru: po startu bez `SEED_DEMO` je tam jedna automatizace a nula ukazkovych ticketu i incidentu, token webhooku sedi s promennou, provoz na te same adrese zaklada tickety, krok vyplni vsechna pole vcetne vlastnich a oba widgety pocitaji a prokliknou se. ## 2026-08-13 - krok prirazeni a ukazkova data nesahaji na provoz ### Opraveno - **`ticket/assign` nemel vykonnou cast**, takze krok "Prirad resiteli" vzdycky selhal hlaskou "operace nema vykonnou cast". Doplnen jako vnitrni krok. - **Ukazkova automatizace "Smerovani ticketu na resitele" je vypnuta.** Zapnuta prebirala tickety, ktere uz nekomu patrily podle skutecne automatizace zakaznika - ukazkova data nemaji sahat na zivy provoz. - Do udaju spoustece pribylo `assigned` a `knownCustomer`, aby slo napsat podminku "uz je prirazeny, nesahej na to". Bez toho nemela z ceho vychazet. ### Zmeneno - `ticket/upsert` prijima `status`. Kdyz hodnota patri mezi nase ctyri stavy, nastavi stav; jinak se ulozi jako **faze**, protoze to presne je - cizi aplikace posila svoje stavy hovoru a nas zivotni cyklus je pevny. Do shrnuti kroku se napise, co se stalo, aby to nebylo kouzlo. ### Overeno Pripad z provozu: telo `{callSid, status, voicebotId}`, callSid do externiho ID, voicebotId jako stitek, status jako stav. Ctyri zpravy o trech hovorech daly **tri tickety**, filtr na stitek vratil jen hovory daneho voicebota a widget je spocital (vb-77: 2, vb-88: 1) vcetne prokliku na vyfiltrovany seznam. ## 2026-08-13 - fronta, worker a spoustece Popis v [20-fronta-a-runtime.md](20-fronta-a-runtime.md). ### Zmeneno zasadne - **Webhook uz nic nevykonava v requestu.** Zapise udalost do fronty a odpovi 202 do jednotek milisekund. Strom vykona worker na pozadi. Za konektory nerucime, takze cekat na cizi sluzbu v requestu znamena ztracet udalosti. ### Pridano - `runtime/queue.ts`: fronta behu v ulozisti. Opakovani s rostouci prodlevou (30 s, 2 min, 10 min, hodina), spravedlive poradi po firmach, navrat zaseknutych behu po restartu, uklid hotovych. - `runtime/worker.ts`: bere praci z fronty, ctyri behy naraz. - `runtime/triggers.ts`: tri druhy spoustecu. Push (webhook), vnitrni udalost (vznik a zmena ticketu) a **pull, tedy pravidelne dotazovani** u sluzeb, ktere webhooky nemaji - posta, zpravy. Planovac jen rekne "je cas", samotny dotaz je prvni krok stromu. - **Kontrakt tela webhooku.** Kazdy parametr ma cestu (`data.order.id`, `errors.0.message`), takze jde napojit i odesilatel s vnorenym modelem. U adresy je videt metoda, ukazka tela podle parametru a kopiruje se cela adresa vcetne domeny. - Vnitrni kroky: `ticket/upsert` (zaloz nebo doplň podle externiho ID), `assign-least-busy`, `assign-by-external`, `set-type`, `set-stage`, `add-tags`, `set-status`, `incident/create`, `flow/pause`, `flow/log`. - **Faze ticketu** (`stage`) jako treti osa vedle stavu a stitku. Stav je zivotni cyklus a pocitaji se z nej statistiky, faze je workflow daneho typu a muze byt jen jedna, takze se na ni da spolehnout v podmince. - **ID z cizich aplikaci u resitele** (`externalIds`). Voicebot posle `voicebotId` a ticket skonci u toho, komu patri. Vazba je na jednom miste, ne v kazde automatizaci. - **Upozorneni**: komu prijde ticket, ten to vidi hned, vcetne cisla u zalozky. - **Incident z kazde chyby** se dvema urovnemi: `impact` cte klient a je srozumitelny, `detail` cte admin a je v nem cely beh, ktery krok selhal, cele hlaseni a data na vstupu. `detail` se vraci jen spravci platformy. - **Ochrana proti smycce.** Automatizace navazana na zmenu ticketu ticket meni, cimz se spousti znovu - pri vyvoji to server polozilo. Resi to oznaceni behu (`AsyncLocalStorage`) a strop peti behu na ticket za minutu. - Zivy dashboard: dlazdice nad nasimi daty se prekresli na udalost, data z konektoru drzi server podle `ttlSec` a jde vynutit nacteni znovu. ### Opraveno - `path` a `intervalSec` u spoustece se pri ulozeni zahazovaly, takze kontrakt webhooku nefungoval. - Nad seznamem neslo pouzit `contains`, takze na stitky neslo postavit podminku. Prave na tom stoji prideleni prace. - Novejsi vystup kroku ted prekryje starsi se stejnym jmenem. Driv to builder hlasil jako konflikt i tam, kde zadny nebyl. - Marna chyba (chybejici skript, neexistujici skupina) se uz neopakuje petkrat. ### Overeno Dva scenare proti bezicimu serveru, 34 kontrol celkem: 1. **Firma se skladem, expedici a IT.** Webhook odpovedel za 12 ms, worker zalozil ticket, dal mu typ a stitek, druha automatizace ho podle typu a stitku predala nejvolnejsimu ze skladu. Druha objednavka sla jinemu cloveku. Chyba z prevodniku dokladu prisla vnorenou cestou, skoncila u IT a zalozila incident. 2. **Hovory z voicebota.** Telo `{callSid, status, voicebotId}`: callSid do externiho ID, status do faze, prirazeni podle voicebotId. Tri zpravy o tomtez hovoru daly **jeden ticket** se tremi udalostmi. Neznamy voicebot neskoncil tise - je videt ve fronte i jako incident. ## 2026-08-13 - runtime, prokliky z widgetu a kapacitni rozbor ### Pridano - `src/runtime/executor.ts`: **strom se konecne vykonava**. Jde krok po kroku, u podminky se vetvi, do poli dosadi `{{parametry}}`, akci pusti pres `runScript` a vystupy pripise do kontextu, aby na ne mohl dalsi krok odkazat. Cely prubeh jde do logu ticketu vcetne toho, co sluzba vratila. Pouzivaji ho **obe** cesty: akce na ticketu i webhook automatizace. - Z widgetu se da prokliknout na to, co je za cislem: z lidi na cloveka, ze seskupeni na uz vyfiltrovany seznam ticketu. Odkazy sklada **server**, protoze on jediny zna filtr widgetu. - Seznam ticketu cte filtr z adresy (`?typeId=`, `?tag=`, `?assignee=`, ...), takze proklik vede na spravny vyber a odkaz jde poslat kolegovi. - Seznam ticketu umi filtrovat na typ, tag a skupinu. - Spolecna `TicketTable`: jedna tabulka pro seznam i pro detail osoby. **Na mobilu se neposouva do strany**, uzka obrazovka dostane karty. - [19-kapacita-200-firem.md](19-kapacita-200-firem.md): zmereny rozbor toho, co se stane pri 200 firmach, a co to bude chtit za server. ### Opraveno - **Rozlozeni dashboardu s vlastnim widgetem se nedalo ulozit.** `validateLayout` znala jen vestaveny katalog, takze kazdy pokus skoncil hlaskou, ze widget v katalogu neexistuje. Katalog je ted jedna funkce (`widgetCatalog`) a pouziva ji nabidka i kontrola. - Katalog widgetu je za konkretni firmu, ne za vsechny firmy uzivatele. Driv slo polozit dlazdici jedne firmy na dashboard druhe, kde k ni data nikdy neprisla. ### Odebrano - **Simulace** vcetne tlacitka, dialogu i endpointu `/api/simulate`. Provoz se ted dela prijmem udalosti, ktery je skutecny. - Trojice pohledu nad tickety. Vyber firmy je select, "moje" je prepinac - driv to delalo totez dvakrat. ### Overeno 7 kontrol proti bezicimu serveru: ulozeni rozlozeni s vlastnim widgetem, oddeleni katalogu po firmach, vykonani stromu akce i webhooku, zapis behu do logu, odkazy z widgetu a filtrovani podle nich. K tomu mereni na 5 000 ticketech, ze ktereho vychazi rozbor kapacity. ## 2026-08-13 - ticketovaci system: udalosti, externi ID, statistiky, widgety Popis v [18-ticketovaci-system.md](18-ticketovaci-system.md). ### Pridano - prijem udalosti - `POST /webhook/ticket/:token`: **jakakoliv udalost se muze stat ticketem**. Token patri firme, ne automatizaci, takze zalozit ticket jde i bez stromu. - `externalId` na ticketu, **unikatni v ramci firmy**. Dalsi udalost se stejnym ID se navesi na existujici ticket misto zalozeni druheho. Cislo a retezec jsou tentyz klic, jinak by `3` a `"3"` byly dva tickety. - Udalosti se u ticketu drzi cele vcetne prijatych dat a jdou rozbalit v detailu. Je to neco jineho nez log: log je nase stopa, udalost fakt zvenku. ### Pridano - co se meri - `firstResponseAt`, `resolvedAt`, `resolvedById` a `reopenCount` na ticketu. Bez nich neslo rict, kdo kolik odbavil ani jak dlouho zakaznik cekal. - `getAgentStats`: odbavene za obdobi, fronta, medianove casy do vyreseni a do prvni reakce, vracene tickety. Median zamerne, ne prumer. - Widget **Vykon resitelu** a detail osoby pouzivaji tutéž funkci, takze cisla sedi na obou mistech. ### Pridano - widgety - Klient konecne vola `/api/dashboard/widget-data`. Predtim endpoint existoval, ale nikdo ho nepouzival, takze vlastni widget hlasil "nepodarilo se zobrazit". - Novy zdroj `connector`: co umi zjistit napojena sluzba, jde vytahnout do dlazdice. Vola se tentyz skript jako v kroku automatizace, vysledek se cachuje (`ttlSec`, nejmene 30 s). - Mrtvy odkaz v rozlozeni jde v rezimu uprav odstranit, ne jen precist. ### Zmeneno - **Akce a widgety maji vlastni zalozku**, uz nejsou v nastaveni. Je to definice toho, co aplikace umi, stejna uroven jako automatizace. - **Telo akce se sklada stromem**, ne JSONem v textarei. Je to tentyz editor jako u automatizaci, jen misto karty spoustece je "spousti clovek". - Nova zalozka **Lide** se seznamem resitelu a detailem osoby. - Seznam ticketu i lidi ma **dva pohledy**, tabulku a dlazdice. - Dlouhe pomlcky pryc z celeho projektu (48 znaku ve 23 souborech). ### Opraveno - `createTicket` bral `typeId`, `fields`, `tags` i `assigneeGroupId`, ale **nikdy je neukladal**. Ticket zalozeny s typem tak zustaval bez typu a bez vlastnich poli. ### Overeno 21 kontrol proti bezicimu serveru v rezimu souboru: navazani druhe udalosti na tentyz ticket, oddeleni firem pri stejnem externim ID, ulozeni typu a poli z prijmu, vyreseni s casem i clovekem, zapocteni navratu z vyreseno, cisla ve widgetu vykonu, cele chybove hlaseni u widgetu nad nenapojenym konektorem, detail osoby, rozsah parametru akce, ulozeni stromu akce a navigace. ## 2026-08-13 - firmy, prava, typy ticketu, akce, widgety a uloziste pro vsechno Dodelany cely [navrh rozsireni](09-navrh-rozsireni.md) a vsechna data se ukladaji. Rejstrik novych funkci a komponent je v [15-rejstrik-funkci.md](15-rejstrik-funkci.md). ### Pridano - obecne vrstvy - `src/data/store/`: jedno rozhrani `EntityStore` a dve implementace, soubor nebo pamet (`local.ts`) a Postgres nad tabulkou `records` (`postgres.ts`). Volajici nepozna, ktera bezi. Nova entita znamena jeden `defineStore` a jeden radek v `bootstrap.ts`. - `store/cached.ts` (`withCache`) pro konfiguracni entity ctene pri kazdem requestu a `store/mirror.ts` (`withMirror`) pro provozni data, ktera se meni v pameti a po zmene se cela zapisuji. Dva pomocniky, ne osm skoro stejnych souboru. - `put` v rozhrani uloziste: zapis celeho zaznamu bez skladani patche. - `src/routes/crud.ts`: fabrika CRUD rout. Seznam, detail, zapis, mazani, pravo a audit na jednom miste. - `components/dashboard/EntityAdmin.tsx`: obecna sprava zaznamu na klientovi. Nova zalozka nastaveni je popis sloupcu a poli, ne nova stranka. ### Pridano - funkce - Firmy, uzivatele, clenstvi, osoby a skupiny resitelu se spravuji z portalu. - Role a prava jsou **data**, ne pevny seznam v kodu: katalog 26 prav, vlastni role za firmu, systemove role nejde menit. Navigace chodi ze serveru jako prunik toho, co firma ma, a toho, na co ma clovek pravo. - Typy ticketu s vlastnimi poli, tagy a vlastni workflow stavu. - Vydefinovane akce na ticketu: vazba na **typ nebo tag**, telo je jedna operace, vlastni strom, nebo skript. Server posila jen akce, ktere v dane situaci projdou podminkami a pravy - klient si nepocita, co ukazat. - Vlastni widgety dashboardu vcetne dat na jeden request a seskupeni. - Audit a prepnuti spravce na jiny ucet. Prepnuti je **vychozi jen pro cteni**, zapis se musi zapnout vedome a je videt v auditu. - Detail ticketu: CTA akci, typ, tagy, vlastni pole a prehozeni na skupinu. ### Pridano - uloziste pro provozni data Tickety vcetne logu, automatizace, incidenty a rozlozeni dashboardu se po kazde zmene zapisuji. Citace ID se pri startu dopocitaji z ulozenych zaznamu, takze novy ticket nikdy neprepise stary. ### Overeno V rezimu `file`: zmena typu, tagu, vlastnich poli, stavu, komentare, nova automatizace, novy incident a upravene rozlozeni dashboardu prezily **tvrde ukonceni procesu** a po restartu byly zpatky vcetne logu ticketu. Novy ticket dostal dalsi cislo, ne cislo existujiciho. Agent bez prav dostane 403 na spravu roli a uzsi navigaci. `passwordHash` se nikdy nevraci. Databaze se v tomhle kole neoverovala, nebylo na cem - kod pro ni je stejny a psany soucasne, ale nebezel. ## 2026-08-12 - soubor jako uloziste bez databaze Mockup se k databazi nedostane, takze pribyl treti rezim: JSON soubor. Prezije restart procesu i containeru, ale ne redeploy. ### Pridano - `src/data/snapshot.ts`: atomicky zapis (`.tmp` a prejmenovani), slucovani zapisu a dokonceni rozepsaneho zapisu pri `SIGTERM`. Rozbity soubor se prejmenuje na `.broken`, zaloguje a jede se s prazdnymi daty - aplikace, ktera nenastartuje, je pro AppFactory nefunkcni sluzba. - `src/data/connectors/local.ts`: jeden kod pro pamet i soubor, lisi se jen tim, kam se zapisuje. Nahrazuje `memory.ts` - dve implementace by se casem rozesly. - Klic k sifrovani se mimo databazi vygeneruje do `DATA_DIR/secrets.key`, takze sifrovani funguje bez nastaveni. U databaze se negeneruje: kdo ma zalohu tabulky, ma i klic ze stejneho stroje. - `DATA_DIR` v konfiguraci, `data/` v `.gitignore` a `.dockerignore`. - Hlaska v portalu rozlisuje tri nasledky: pamet (ztrata pri restartu), soubor (ztrata pri redeployi) a databaze (bez ztraty, hlaska se nezobrazuje). ### Overeno Bez databaze: konektor s vyplnenymi udaji prezil restart, v JSONu jsou hodnoty sifrovane a plaintext v nem neni. S databazi: rezim `postgres` funguje dal a klic vedle dat se nevygeneroval. ## 2026-08-12 - databaze pro konektory Konektory se ukladaji do Postgresu, pristupove udaje sifrovane. Popis v [14-databaze.md](14-databaze.md). ### Pridano - `pg` jako zavislost, pool v `src/db/pool.ts` vcetne transakci a `dbFor(tenantId)` jako sev pro budouci oddelenou databazi jednoho klienta. - Migrace ze souboru `src/db/migrations/*.sql`, pousti se pri startu pod `pg_advisory_lock` - pri rolling deployi je jinak pusti vsechny instance naraz. Jeden soubor je jedna transakce, takze pri chybe nevznikne rozdelane schema. - Sifrovani pristupovych udaju (`src/db/secretBox.ts`), AES-256-GCM s nahodnym IV a verzi klice. Nerozsifrovatelna hodnota nepada, chova se jako nevyplnena a zaloguje se - jeden rozbity konektor nesmi shodit seznam ostatnich. - Dve implementace uloziste konektoru za jednim rozhranim (`memory`, `postgres`). Rozhodnuti je jen na jednom miste, v `src/data/connectorStore.ts`. - `/health/ready` s pingem do databaze. `/health` na databazi zamerne nezavisi: kratky vypadek DB by jinak vedl k restartovani containeru. - `GET /api/dashboard/storage` a hlaska na strance Konektory o tom, ze data jsou jen v pameti. Bez toho se clovek divi, kam se podely jeho konektory. - Jediny vychozi konektor na firmu a sluzbu hlida castecny unikatni index, ne jen kod. Dva soubezne zapisy by jinak udelaly dva vychozi. ### Zmeneno - Cteni i zapis konektoru je asynchronni, vcetne validace stromu. - Databaze je volitelna. Bez `DATABASE_URL` nebo `SECRETS_KEY` se jede v pameti a rekne se to v logu i v portalu. Container, ktery nenastartuje, je pro AppFactory nefunkcni sluzba. - Kdyz jsou migrace nastavene a selzou, jede se dal v pameti. Psat do rozbiteho schematu je horsi nez neukladat. ### Overeno Proti Postgresu 16 v kontejneru: migrace, sifrovani v tabulce, preziti restartu, rozsifrovani spravnym klicem, degradace pri spatnem klici, `PATCH` bez tajneho pole, prepnuti a smazani vychoziho konektoru, a pametovy rezim bez `DATABASE_URL`. ## 2026-08-12 - transformace dat a oprava konektoru Transformace dat popsana v [13-transformace-dat.md](13-transformace-dat.md). ### Pridano - Kroky si predavaji i cele struktury. `FieldType` ma `object` a `list`. Do sablony se nedosazuji, predavaji se jako celek dalsimu kroku - proto je u nich v builderu vyber a ne textove pole. Z podminek nad nimi ma smysl jen "prisla / neprisla". - Strop na velikost struktury (`SCRIPT_MAX_VALUE_BYTES`, vychozi 256 kB). Radek s vystupem kroku je nejrychleji rostouci tabulka v systemu. - `src/scripts/mapping.ts`: cesty, prevody, pravidla a sablona JSON. Skripty ho dostanou na `ctx.util` jako `get`, `applyRules` a `fillJson`. - Dva rezimy transformace: `transform.map-fields` (pole na pole s prevody) a `transform.to-json` (sablona cileveho objektu s `${cesta}`). V sablone je marker `${...}` zamerne jiny nez `{{...}}`: sablony kroku se dosazuji driv, nez krok bezi, a stihly by ji prepsat. - Prevod `map` pro seznamy. Bez nej by slo prevest hlavicku dokladu, ale ne polozky objednavky, a doklad by byl na nulu. - `idoklad.create-invoice-from-object`: druha polovina prikladu, bere hotove telo dokladu z transformace. - Spoustec e-shopu predava celou objednavku jako objekt a polozky jako seznam. - Klikaci editor pravidel v builderu vcetne rezimu JSON pro vnorena pravidla. - Kontrola JSONu a tvaru pravidel uz pri ulozeni stromu. Preklep je nedodelek, ne chyba ukladani - rozdelana prace se nezahazuje. ### Opraveno - **Konektor neukladal pristupove udaje.** Server byl v poradku, chyba byla v prohlizeci: u pole `type="password"` prohlizec ignoruje `autocomplete="off"` a dosazuje ulozene prihlaseni. Uzivatel pak videl jednu hodnotu, React drzel jinou, a ulozilo se to, co drzel React, tedy nic. Resi to `autocomplete` `new-password`, jmena poli, ktera nepripominaji heslo, a prepinac zobrazeni, aby slo overit, co je opravdu zapsane. - Zakladani a uprava konektoru se presunuly do dialogu. Na strance jsou jen male karty - formulare rozlozene po strance byly u vic konektoru neprehledne. ## 2026-08-12 - sluzby a konektory Rozdeleni na sluzbu a konektor. Popis v [12-sluzby-a-konektory.md](12-sluzby-a-konektory.md). Slovo "konektor" driv v kodu znamenalo katalog toho, co umime. Ted znamena napojeni jedne firmy, tedy to, co tim mysli i uzivatel. ### Pridano - `src/data/services.ts`: sluzba nese `general`, `appId`, `visibility`, `credentials` a `verifyPath`. Kategorie `obecne` sdruzuje veci, ktere ma kazdy a nepotrebuji konektor: webhook, planovac, tickety, transformace dat, HTTP pozadavek, pauza, log. - Viditelnost sluzby: vsichni, jen uvedene firmy a lide, nebo jen spravce platformy. Neviditelna sluzba se z API nevraci vubec. - `src/data/connectorStore.ts`: konektory za firmu vcetne hodnot pristupovych udaju. Hodnoty se z API nikdy nevraci, jen `filled` a `missing`. - `FlowStep.connectorId`: krok rika, pod kterym napojenim se ma volat. `null` = vychozi konektor firmy, takze vzorovy strom je prenositelny. - Overeni konektoru pres `verifyPath`, tedy cteci volani vyzadujici autorizaci. U sluzby bez nej se overi jen dostupnost a odpoved to rekne nahlas. - Stranky `/dashboard/sluzby` a `/dashboard/konektory` vcetne formularu udaju. - Endpointy `/api/dashboard/services` a CRUD `/api/dashboard/connectors` vcetne Swaggeru. - Predvyplnene prihlaseni spravcem platformy a prepinac demo uctu na login strance. Kvuli testovani prototypu, pred ostrym pouzitim odebrat. ### Zmeneno - **Pristupove udaje se prestaly cist z environment variables.** Cela instance by mela jedny udaje spolecne a dve firmy by fakturovaly z jednoho uctu. Z prostredi zustava jen `SERVICES_BASE_URL`. - Stav "napojeno" se prestal cist z katalogu a zacal pocitat z konektoru firmy. Sluzba ma jen `available` nebo `planned`. - Prejmenovani napric kodem: `Connector` na `Service`, `FlowStep.connectorId` na `serviceId`, `GET /connectors` na `GET /services`, `connectorIcons` na `serviceIcons`, stranka Konektory (katalog) na Sluzby. Prevodni tabulka je v dokumentu 12. - Validace stromu overuje i konektor. Cizi konektor je chyba, chybejici napojeni nedodelek. ## 2026-08-12 - skripty konektoru Naprogramovana vykonna cast konektoru. Popis je v [11-skripty-konektoru.md](11-skripty-konektoru.md). ### Pridano - `scripts/` se skripty konektoru. Jeden soubor nese manifest (vstupni a vystupni parametry) i kod. Obycejny JavaScript, aby se nemusel prekladat. - Hot reload podle casu zmeny souboru. Uprava v portalu i rucni uprava souboru se projevi bez restartu. - Kontrola vstupu i vystupu proti manifestu, jedna funkce pro obe strany. Chybejici povinny vystup je chyba skriptu, ne uzivatele. - `ctx` predavany skriptu: `http` nad adresou napojeni, `util`, `log`, `config`, `idempotencyKey`, `fail` a `retry`. Skript nedostane pristupove udaje. - Rozliseni opakovatelne a koncove chyby. Runner nikdy nevyhodi vyjimku, vzdy vraci vysledek vcetne `retryable`. - Redakce tajnych hodnot pred zapisem do logu. - Napojeni z environment variables (`src/scripts/connections.ts`) vcetne iDokladu. - Sest ukazkovych skriptu pro iDoklad proti skutecnemu API sluzby `services.csbot.cz/apps/idoklad`, kazdy na jiny vzor. - Stranka `/dashboard/skripty`: seznam, manifest, editor, zkusebni spusteni. Formular testu se sklada z manifestu, nepise se pro kazdy skript. - Endpointy `/api/dashboard/scripts`, `/:id`, `PUT /:id`, `/:id/test` a `/reload`. Vse ve Swaggeru. ### Zmeneno - Katalog konektoru uz neni jen staticky seznam. Akce ze skriptu se domeruji prekryvem v `src/data/connectors.ts`, takze se naraz objevi ve validaci stromu, ve vypoctu scope i v sablonach. Pri stejnem ID operace vyhrava skript. - `ConnectorOperation` ma `implementation` a `scriptId`. Katalog v portalu operace se skriptem oznacuje ikonou. - `ApiError` na klientovi nese cele telo odpovedi a umi z nej vytahnout `issues`. - Dockerfile kopiruje `scripts/` do vysledneho image. ### Vedome neudelano Skripty bezi v procesu serveru, ne v sandboxu. Jsou nase a prosly gitem. Zakaznicke skripty budou potrebovat izolovany engine ve vlastnim vlakne, duvod je v [10-runtime-a-kapacita.md](10-runtime-a-kapacita.md). Ulozeni z portalu zapisuje do souboru v containeru. Bez trvaleho svazku ho redeploy vrati na verzi z gitu. ## 2026-08-12 - navrhy Pridany [09-navrh-rozsireni.md](09-navrh-rozsireni.md) a [10-runtime-a-kapacita.md](10-runtime-a-kapacita.md). 09 popisuje datove modely: akce navazane na typ nebo tag ticketu s telem jako operaci, vlastnim stromem nebo skriptem, typy a tagy ticketu, role a prava jako data misto unionu, zalozky a zpristupneni konektoru za firmu, konektory rozdelene na definici, zpristupneni a napojeni, cekaci krok, sablony zprav, vlastni widgety se seskupovanim a prevod na Postgres. Soucasti je kontrola navrhu proti celemu prikladu se dvema firmami jednoho cloveka. 10 popisuje vykonnou cast: cestu udalosti od webhooku pres inbox a dispatcher k workeru, frontu v Postgresu se `SKIP LOCKED`, davkovy odber, spravedlnost mezi klienty, idempotenci, retence a rozpocet na 150 klientu ve dvou scenarich objemu. Nic z toho neni naprogramovane, oba dokumenty jsou navrh k rozhodnuti. Kod se nemenil. ## 2026-08-03 Tickety predelane na plnohodnotny konektor. Prestavaji byt polozkou v seznamu a stavaji se prichozim pozadavkem, ktery ma sveho cloveka a dohledatelny prubeh. Popis modelu je v [06-tickety.md](06-tickety.md). ### Pridano - Kanaly do ticketu: WhatsApp jako novy konektor, e-mail a hlasova linka jako plnohodnotne spoustece. - Konektor Tickety presunut do nove kategorie `servicedesk`, rozsiren o spoustece `created`, `unknown-customer`, `assigned`, `status-changed` a akce `assign`, `set-status`, `link-customer`. - Resitele (`src/data/people.ts`) oddelene od uzivatelu portalu, spojka e-mailem. - Log ticketu ve strome vcetne toho, co ktera volana sluzba vratila. - Prehled vytizeni tymu, kdo co ma u sebe, zaroven jako filtr seznamu. - Detail ticketu `/dashboard/tickety/:id`: prubeh a log, prirazeni, stav, zakaznik, komentare. - Filtry seznamu ticketu na serveru: resitel (vcetne `me` a `unassigned`), stav, kanal. - `providedFields` v katalogu konektoru: parametry, ktere spoustec predava sam. - Udalost `ticket.assigned` na sbernici i v portalu. - Endpointy `/api/dashboard/people`, `/tickets/workload`, `/tickets/:id` a POST varianty pro assign, status a comment. Vse ve Swaggeru. ### Zmeneno - `Ticket` ma misto volneho `requester` strukturovaneho `customer` s nullable `id` firmy v CRM, k tomu `channel`, `assignee` jako odkaz na cloveka a `automationId`. - `customer.id === null` je nosna informace, ne chybejici udaj. Prave na ni se pta podminka "mame zakaznika?" ve strome automatizace. - Simulace ticketu bere kanal a prepinac, jestli se zakaznik dohleda. Zakladany ticket dostane cely realisticky log. - Builder ukazuje parametry od sluzby jen ke cteni. Server je pri ulozeni vzdy dosadi z katalogu, a to jeste pred validaci stromu. ### Vedome neudelano Bugs a wishes zustavaji mimo. Vyvojarska agenda ma jiny zivotni cyklus a slucovat ji s tickety by znamenalo, ze ani jedna evidence nefunguje poradne. ### Doplneno pote Puvodni verze mela diru: ticket nemel zadny obsah a krok "Zalozit ticket" nesel nastavit. Slo tedy rict "z WhatsApp udelej ticket", ale ne uz co se ma kam ulozit. - `Ticket.body` a `Ticket.sourceRef`. Predmet je shrnuti, telo je cely text pozadavku. `body` vystaveno i ve spoustecich `created` a `unknown-customer`, takze na obsah ticketu jde udelat podminka v navazne automatizaci. - Nastavitelna pole akci (`OperationField` a `FlowStep.inputs`). Ticket, e-mail a WhatsApp maji skutecna pole misto pouhe napovedy. - Sablony `{{parametr}}` v hodnotach poli (`src/data/templates.ts`) vcetne nabidky parametru, ktera je vklada na pozici kurzoru. - Vyber resitele u akci se plni ze seznamu lidi, ne z rucne psaneho ID. - Nevyplnene povinne pole a odkaz na neexistujici parametr se hlasi jako nedodelek. Nastaveni pole, ktere akce nema, je chyba 400. - Akce bez `inputs` to v builderu napisou primo na karte kroku. ### Vystupy kroku a predvalidace Druha dira: kroky slo vkladat kamkoliv, ale podminka videla jen parametry spoustece. Slo tedy pridat krok "zeptej se CRM", ale ne se vetvit podle toho, co vratil. Bez toho byla predvalidace k nicemu. - `outputFields` v katalogu: co akce vrati dalsim krokum. Ma je "Dohledat firmu" (`customerKnown`, `companyId`, `companyName`), "Zaradit do kategorie", "Zalozit obchodni pripad" i "Zalozit ticket". - Nova akce RAYNET "Dohledat firmu". Nic nezaklada, jen odpovi, jestli odesilatele zname. Presne pro predvalidaci. - `src/data/flowScope.ts` pocita, co je videt v kterem miste stromu. Krok vidi spoustec plus vystupy kroku pred nim. Vetev nepridava nic do sekvence za podminkou, protoze nemusela probehnout. - Builder nabizi v podmince i v polich akce presne ty parametry, ktere v danem miste doopravdy jsou. - Odkaz na parametr, ktery ve strome neni, je chyba 400. Odkaz na parametr, ktery vznika az pozdeji, je nedodelek s radou posunout podminku niz. - Duplicitni jmeno parametru ve scope je nedodelek. V sablone by nesl poznat, ktery se dosadi. ### Kanaly a vzorove automatizace - Konektory Facebook Messenger a Instagram, kanaly `facebook` a `instagram` u ticketu. - Ctyri nove vzorove automatizace v rozdeleni, ktere odpovida zameru: jedna na kanal pro prijem, jedna spolecna pro smerovani na resitele. Prijmove zamerne neprirazuji, smerovani si ticket prevezme a podminkou `assigned neni splneno` neprepise rucni rozhodnuti. ### Firmy a prava Treti a nejvazneji dira: portal nemel zadnou tenanci. Kterykoliv prihlaseny uzivatel videl vsechny tickety vsech firem a cely seznam resitelu, `requireRole` se nikde nevolal. Popis v [07-firmy-a-prava.md](07-firmy-a-prava.md). - Tenant jako hranice viditelnosti. `tenantId` na ticketu, resiteli i automatizaci. - Uzivatel muze patrit do **vic firem**, v kazde s jinou roli (`memberships`). Pristup napric firmami je zvlast jako `platformAdmin`. - Tri pohledy na tickety: `all`, `tenant`, `mine`. Admin mezi nimi prepina, vcetne vyberu firmy. - `src/data/access.ts` jako jedine misto, kde se rozhoduje o pravech. `GET /api/dashboard/access` rekne klientovi, co smi kreslit. - Filtr na firmu je v ulozistich **povinny argument**. Zapomenuty filtr neznamena "vse", ale nezkompiluje se. - Nikdy tise nezuzujeme. Cizi firma vraci 403 nebo 404. - Prirazeni jen v ramci firmy. Prehazovat praci mezi lidmi smi jen admin, agent si smi vzit ticket na sebe. - `requireRole` nahrazen `requirePlatformAdmin`. Prava uvnitr firmy resi `access.ts`, protoze zavisi na tom, ktera firma pozadavek zajima. - Demo ucty pokryvaji vsechny tri situace vcetne cloveka ve dvou firmach. ### Nastavitelny dashboard Prehled byl pevne dany. Ted si ho kazdy sklada sam, popis v [08-dashboard-widgety.md](08-dashboard-widgety.md). - Katalog widgetu na serveru vcetne toho, ktere sirky ktery widget unese. Graf v tretine sloupce se necte, proto se tam ani nenabizi. - Rezim uprav na `/dashboard`: pridani z nabidky, vyber sirky, poradi sipkami, odebrani. Ulozi se az tlacitkem, Zrusit vrati puvodni stav. - Rozlozeni se uklada za **dvojici uzivatel a firma**, ne jen za uzivatele. - Data nacita prehled a rozdava je widgetum. Deset dlazdic tak neznamena deset stejnych dotazu. - Server rozlozeni overuje proti katalogu. Neznamy widget nebo nepodporovana sirka se neulozi, widget zmizely z katalogu se v prehledu ukaze jako chyba, ne ze tise zmizi. ### Zapsano jako otevrene rozhodnuti Vsechny automatizace se stejnym spoustecem se spusti. Doporucene rozdeleni na to nenarazi, ale az se bude psat runtime, musi se to rozhodnout vedome. Varianty a doporuceni v [06-tickety.md](06-tickety.md). ## 2026-07-31 Prvni nasazeni aplikace do repozitare csbot-prototype. Puvodni sablona byla holy Express s endpointy `/` a `/health`. Nahradil ji kompletni web a klientsky portal. ### Pridano - Verejny web: homepage se sekcemi, sluzby, o nas, kontakt s formularem, 404. - Prihlaseni pres JWT s demo ucty. - Klientsky portal: prehled s grafem, tickety, incidenty, automatizace, konektory, nastaveni. - Builder automatizaci: strom akci, spoustec, vetveni podminkou. - Katalog 25 konektoru v 8 kategoriich. - Webhook s registrovanou adresou, token generuje server. - Zivy dashboard pres SSE, vcetne indikatoru spojeni a bublin s udalostmi. - Simulace provozu pod tlacitkem v postrannim menu portalu. - Swagger UI na `/docs` a OpenAPI definice na `/openapi.json`. - Dokumentace ve slozce `documentation/`. - `.gitignore`, ktery drzi `node_modules` a `dist` mimo repozitar. ### Zmeneno oproti sablone - Aplikace prepnuta na ESM (`"type": "module"`) a `module: NodeNext`. - Jeden container obsluhuje API i zbuildovanou React aplikaci z `dist/public`. - Dockerfile buildu je server i web, vysledny image dostava jen `dist`. - Port zustava 3000, naslouchani na `0.0.0.0` beze zmeny. ### Reseni reverse proxy - `ROOT_PATH` se cte z prostredi, nikde neni hardcoded. - Aplikace se mountuje na koren i na prefix, funguje tedy at Caddy prefix odstrani nebo ne. - Server vklada do `index.html` znacku `` a `window.__BASE_PATH__`, aby SPA nasla soubory i na vnorenych cestach. - OpenAPI `servers` obsahuje prefix, takze Swagger Try it out vola spravnou adresu. - Router ma `strict: true`, jinak by se presmerovani `/docs` na `/docs/` zacyklilo. ### Overeno lokalne S `ROOT_PATH=/apps/csbot-prototype`: - `/apps/csbot-prototype/health` i `/health` vraci 200, - `/apps/csbot-prototype/docs` presmeruje na `/docs/`, ta vraci Swagger UI, - `swagger-ui.css` se nacte pres prefix, - OpenAPI `servers` obsahuje `/apps/csbot-prototype`, - `index.html` na vnorene ceste obsahuje spravny ``, - prihlaseni pres prefix vraci token, - neexistujici cesta pod `/api` vraci JSON, ne HTML aplikace. ### Znama omezeni Data jsou v pameti, restart je vrati do vychoziho stavu. Obsah webu je ukazkovy. Ulozeny strom automatizace se nevykonava.