# 99 - Zaznam zmen
Nejnovejsi nahore.
## 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.