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 - prave to nas dnes stalo pul hodiny hadani, proc je na produkci porad stary vzhled. - oznaceni buildu v pate postranniho menu, pod tlacitkem Odhlasit se: verze, cas buildu a cislo commitu. Text jde oznacit jednim kliknutim. - totez jako <meta name="app-build"> 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 presne to clovek potrebuje pri zjistovani, co bezi na produkci. - hodnoty dosazuje Vite pri buildu (define a plugin build-stamp), takze neplati pro repozitar, ale pro ten konkretni balicek. - .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 a kopiruje se do nej jen dist, scripts a migrace. Ve vyvoji se misto casu pise "vyvoj" - cas buildu by tam byl pokazde jiny a nikomu by nic nerekl. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
68 KiB
99 - Zaznam zmen
Nejnovejsi nahore.
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
<meta name="app-build">v hlavicce stranky. V JS balicku uz to je, ale ten se musi stahnout a rozbalit. Meta znacka je videt na jedencurlbez prihlaseni - a prave to clovek potrebuje, kdyz zjistuje, co bezi na produkci. - Hodnoty dosazuje Vite pri buildu (
definea pluginbuild-stampvevite.config.ts), takze neplati pro repozitar, ale pro ten konkretni balicek.
Zmeneno
.dockerignoreuz 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.gitnedostane, 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.
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 jakobrand-*. Klice zustaly, protoze je pouziva 341 mist; prepsat je vsechny naraz by byla zbytecne velka zmena. Totez u utilitytext-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.
LogoMarkje 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.
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
keypodle 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,hasPermissionihasAnyPermission. 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.
readScopecte 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.createa 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. tenantIddoplnujeapiFetch, 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), uscope=alla mimo/api/dashboard.useApiQueryse 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, 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.helpdeskSourceIds 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.viewahelpdesk.create. Prideluje je admin te firmy pres role, stejne jako u ostatnich prav.
Zmeneno
listTicketsumi filtrovat podle zdroje (helpdeskSourceIds). Vyplnene nahrazuje filtr podle vlastnika, protoze zadavatel vlastnikem neni. Bezny seznam ticketu se nezmenil.getTicketaaddCommentmaji 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
lookupje 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, pravoticket.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
tenantIda 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 jenctx.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<b>prepsal rozvrzeni zpravy a<script>by dosel prijemci do schranky. - Overeni konektoru prihlasenim. Sluzba s
transport: 'smtp'se neoveruje ctecim volanim, ale prihlasenim na server. Nic se neodesila, takze test nikomu nic nedoruci, a bez platneho hesla neprojde. - Vnitrni krok smi rict "zkus to znovu" (
StepOutcome.retryable). Dosud se opakovani u vnitrnich kroku vzdy vypinalo, protoze selhavaly na spatnem nastaveni. Nedostupny posmovni server ale za minutu bezet muze. - Textova verze zpravy. Bez vyplneni se vyrobi z HTML. Klient, ktery HTML nezobrazi, by jinak dostal prazdnou zpravu a filtry nevyzadane posty berou chybejici textovou cast jako priznak spamu.
- Zavislost
nodemailer. Vlastnorucne psany SMTP klient by byl 300 radku, ktere nejde bez schranky overit.
Opraveno
- SMTP server z konektoru nesmi mirit do vnitrni site. Stejne pravidlo jako
u HTTP (
isPrivateHost), protoze adresu vyplnuje firma.
2026-08-26 - katalog stoji na sluzbach, ktere opravdu bezi, a pribyla OpenAI
Katalog popisoval, co chceme umet. U vetsiny sluzeb chybely pristupove udaje,
takze konektor nemel co vyplnit, a appId u nekterych ukazovalo na aplikaci,
ktera neexistuje. Zdroj pravdy o tom, co bezi, je services.csbot.cz/apps
a jeji /openapi.json.
Novy dokument: 21-realne-sluzby.md.
Zmeneno
- Trinact sluzeb katalogu ma napojeni na bezici aplikaci vcetne poli udaju
a levneho cteciho volani na overeni. RAYNET, CSOB, PPL, Microsoft 365, GA4,
Search Console, Google Ads, Sklik a Prepis hovoru mely
credentials: [], takze konektor nesel vyplnit a test se nemel ceho chytit. - Opravena
appId, ktera nikam nevedla. PPL bezi napplcplapi, ne nappl. Microsoft 365 namicrosoft-365-service. Prepis hovoru naaudio-transcription. GA4, Search Console, Google Ads a Sklik bezi vsechny na aplikacianalytics, kazdy s vlastnimi udaji - slucovat je do jedne sluzby by znamenalo, ze firma se samotnym Sklikem musi vyplnit i Google. - Prepis hovoru ma jen operaci, kterou aplikace umi. Operace "Vytvorit souhrn" za sebou nic nemela; souhrn ted udela OpenAI.
- Query ve
verifyPathse do hlasky nedava. Odrizne se stejne jako uScriptRequestInfo- v query muze byt tajemstvi.
Pridano
- Sluzba OpenAI. Firma zada svuj API klic do konektoru a muze se ptat
modelu, poslat soubor a nechat si prepsat zvuk. Skripty
openai.chat,openai.upload-file,openai.ask-about-file,openai.transcribe-audioaopenai.list-models. - Sluzba, ktera nebezi u nas.
Service.baseUrlnese absolutni adresu cizi sluzby. Skladat ji zeSERVICES_BASE_URLby nedavalo smysl, to je zaklad nasich aplikaci. Prepsat ji jde promennou<SLUZBA>_BASE_URL(dosud jen slibenou v dokumentaci, ted opravdu implementovanou) nebo adresou u konektoru, cimz vede cesta na Azure OpenAI. - Predpona hlavicky u pole udaju (
ServiceCredentialField.prefix). OpenAI chceAuthorization: Bearer <klic>; uzivatel vlepi holy klic a slovo pred nim dopise runtime. Preklep v rucne psanemBearerby nesel najit ani zpetne, protoze se hodnota z API nevraci. - Odesilani souboru ze skriptu (
ctx.http.postForm). Obsah prichazi jako Base64, protoze parametr kroku se uklada do zaznamu behu a binarni data by se tam nevesla. StropSCRIPT_MAX_UPLOAD_BYTES, vychozi 10 MB. - Sluzby SAP Business One, Google Workspace a Meta Ads. Bezi, ale
v katalogu nebyly. Meta Ads je zamerne oddelena od Facebook Messengeru
a Instagramu: aplikace
metaje nad Marketing API, tedy reklamy, a je jen pro cteni. - Dvacet tri skriptu k realnym sluzbam, od dohledani firmy v RAYNETu po vykon kampani ve Skliku. Seznam je v 21-realne-sluzby.md.
Opraveno
- Redakce tajemstvi bere i holou hodnotu. Dosud se skrtaly jen hodnoty
hlavicek, takze u
Authorization: Bearer sk-...by samotnesk-...v chybove odpovedi proslo do logu.targetSecretsted vraci obojí a pouziva ho i overeni konektoru, ktere si dosud sestavovalo seznam samo.
2026-08-25 - chybova hlaseni konektoru rikaji, co se stalo
"Pristup zamitnut: GET /apps/idoklad/account/agenda vratilo HTTP 403. Zkontrolujte pristupove udaje." Tahle hlaska je k nicemu. 403 muze byt nepovolena IP adresa i spatne udaje a veta radi presne to, co v tu chvili nepomuze. Duvod pritom sluzba do tela odpovedi napsala, jen se zahodil.
Zmeneno
- Duvod od sluzby jde primo do hlasky.
src/scripts/http.tsvytahne z tela odpovedidetail,error_description,message,title,errori seznammissingHeaders. Retezec, ktery vypada jako JSON, se rozbaluje dal - nase sluzba iDoklad presne takhle predava telo od iDokladu samotneho. Kdyz sluzba nenapsala nic, hlaska to rekne, misto aby to zamlcela. - 401 a 403 uz nejsou jedna hlaska. 401 = udaje sluzba dostala a neuznala, IP adresa s tim nema co delat. 403 = tvar udaju je v poradku, zakazuje se samo volani, tedy IP adresa, opravneni uctu nebo aplikace, pod kterou se vola.
- Cela odpoved sluzby je u overeni konektoru rozbalena rovnou
(
ErrorDetailma novydefaultOpen). U chyby, kterou nikdo necekal, je slozeny toggle to same jako zadny detail.
Pridano
- Cela adresa vcetne serveru v kazde hlasce.
ScriptRequestInfoma noveurl(origin a cesta, bez query - v query muze byt tajemstvi). Do te doby hlaska rikala jen/apps/idoklad/account/agenda, coz nerekne, jestli se to trefilo na spravny stroj, nebo to zaridla cizi proxy cestou. Zaklad adresy je z konfigurace a konektor ho smi prepsat, takze se neda odvodit z toho, kde je nasazeny portal. Adresa je videt i na karte konektoru, v hlavicce dialogu Logy a v odpovedi na test i kdyz projde (baseUrl). - Tlacitko Logy na karte konektoru a historie poslednich peti overeni.
Dosud odpoved sluzby existovala jen v odpovedi na test, tedy do prekresleni
stranky, a v logu containeru. Do logu containeru se nikdo divat nechodi.
Zaznam se uklada i pri uspechu, jinak by neslo poznat, jestli konektor nesel
nikdy, nebo prestal jit ve chvili, kdy nekdo sahnul na udaje.
Migrace
003_connector_checks.sql, endpointGET /api/dashboard/connectors/:id/checks.
Opraveno
- Dialog se vykresluje portalem do
document.body. Samoposition: fixednestaci: rodic sbackdrop-filter(nase.glass, tedy skoro kazdy panel a karta) je pro fixed potomka containing block. Dialog se pak vesel do te karty misto pres celou obrazovku. Tykalo se to vsech dialogu, jen to bylo videt az u Logu, ktere jsou v male karte konektoru.
Pridano pozdeji
-
Vybrane hlavicky odpovedi u chyby (
ScriptError.responseHeaders):server,via,content-type,www-authenticate,retry-after,x-request-id,date. Rikaji, kdo odpoved vydal -Server: Kestrelje aplikace,Via: 1.1 Caddyproxy pred ni. U 403 od proxy byva telo prazdne a bez hlavicek by nezbylo nic. Allowlist, ne vsechno:Set-Cookiea podobne do zaznamu nepatri. -
Odchozi IP adresa portalu (
src/data/egressIp.ts, endpointGET /api/dashboard/connectors/egress-ip). Z containeru neni videt, jak ho protistrana vidi, takze pri 403 od seznamu povolenych IP nebylo co porovnat. Adresa je natvrdo na strance Konektory a u kazdeho odmitnuteho overeni v logu. Zjistuje se echo sluzbou podleEGRESS_IP_URL, drzi se v pameti poEGRESS_IP_TTL_MS, u 401 a 403 se pripoji k zaznamu. PrazdnaEGRESS_IP_URLfunkci vypne. -
Druhe mereni: jak nas vidi nase vlastni proxy. Echo sluzba odpovi verejnou adresu, jenze volani na vlastni domenu se otaci zpatky na tentyz stroj a proxy pak vidi neco jineho, typicky adresu docker bridge. A prave tu porovnava seznam povolenych IP u sluzeb za toutez proxy. Portal proto zavola svoji vlastni verejnou adresu (
PUBLIC_ORIGIN+ROOT_PATH+/whoami) a precte si, jak k nemu volani doslo. Novy endpoint/api/whoamije bez prihlaseni: vraci volajicimu jeho vlastni adresu, nic navic.
Nedoreseno
Proc iDoklad vraci 403, zatim nevime. Vylouceno je to, co posilame: zadna
kombinace hlavicek (Idempotency-Key, Accept, User-Agent) 403 nevyvola,
sluzba na ne odpovida 401 jako na cokoliv jineho. Zbyva zdrojova IP adresa
naseho containeru nebo 403 od iDokladu samotneho, ktere sluzba jen
predava dal. Zvenci to reprodukovat nejde: pres dvacet variant hlavicek
(Idempotency-Key, Accept, User-Agent, jazyk, delka a tvar udaju, duplicitni
hlavicky, HTTP/1.1) vraci vzdycky 401. To ale nic nedokazuje o IP adrese
containeru - z povolene IP se pochopitelne projde. Rozhodnou hlavicky odpovedi
a jeji telo, obojí je nove v portalu pod Logy.
Sonda na /health vedle overeni byla spatny napad a je pryc: /health
povoleni IP adresy nevyzaduje, takze z toho, ze projde, o IP nic neplyne.
2026-08-20 - vystup z vetve plati i za podminkou
Pri stavbe cesty "objednavka -> faktura" vyslo najevo, ze se bezny postup neda poskladat z kroku: najdi zakaznika podle ICO, kdyz neni, zkus e-mail, kdyz porad neni, zaloz ho konci tim, ze ID odberatele dava jednou jedna vetev a jednou druha. Rozsah ale vystupy z vetvi za podminku nepoustel, takze se dal pouzit jen tak, ze se hledani a zakladani sloucilo do jednoho kroku.
To bylo obejiti nasi vlastni chyby, ne reseni.
Zmeneno
- Vystup z vetve je za podminkou k dispozici, jen jako nepovinny
(
required: false). Ze hodnota muze chybet, se neztratilo - builder to u pole ukaze a pri behu se dosadi prazdno, stejne jako u ceho jineho, co neprislo. - Vystup se stejnym jmenem uz z nabidky nemaze ten starsi. Po druhem hledani
kontaktu zmizelo ID z toho prvniho, tedy presne to, co je v tu chvili potreba.
Odkaz se jmenem kroku (
{{st_ico.contactId}}) je jednoznacny, duvod k mazani neni. Hole jmeno ({{contactId}}) porad znamena ten posledni. - Duplicitni jmena u vystupu kroku uz nejsou nedodelek. Dva kroky, ktere
vraci
contactId, jsou bezna vec. Konflikt zustava tam, kde opravdu je: mezi parametry spoustece, kde zadny prefix neni.
Pridano
- Krok Zalozit kontakt (
idoklad.create-contact). Nic nedohledava - kdyz uz kontakt existuje, vznikne druhy, a to je spravne chovani teto operace. Hledani je vlastni krok, takze si strom sam rekne, kdy hledat a kdy zakladat. idoklad.upsert-contact(Najit nebo zalozit) zustava pro toho, komu staci "chci mit jistotu, ze tam je". Obojí ma smysl, ani jedno nenahrazuje druhe.
2026-08-20 - vlastni skripty firmy: prevod dat v JS
Klikaci pravidla jsou u peti poli rychlejsi, ale u modelu objednavky je jich dvacet a v tom se necte. Proto vedle nich skript firmy: prevod z A do B napsany v JavaScriptu, jeden na zakaznika.
Pridano
- Skripty firmy (
src/data/tenantScripts.ts). Uklada se do uloziste, ne na disk - disk je uvnitr kontejneru a redeploy ho vymaze. - Krok Transformace dat - Vlastni skript. Vybere se skript a zdrojova data,
vysledek jde dal jako
{{krok.result}}. - V logu je vstup i vystup. Prave to byl duvod, proc skript nad pravidly vyhral: kdyz vysledek nesedi, neni potreba hadat, co do prevodu vlezlo.
- Zkouska bez ulozeni. V portalu se vlepi skutecne telo a hned je videt, co z toho leze. Bez toho by se chyba poznala az z padleho behu.
- Skripty jsou v zalozce Akce, vedle definic akci. Obojí je popis toho, co
aplikace ve firme umi, a spravuje to tentyz clovek pod pravem
action.manage.
Co skript smi a co ne
Skript je ciste prevod hodnot: dostane input, vrati objekt. Nema require,
import, process, fetch ani console. Volani ven patri do kroku konektoru,
ktery ma pristupove udaje, opakovani i zapis do logu.
| Pojistka | Proc |
|---|---|
| limit 2 s | zacykleny skript by jinak zablokoval workera vsem firmam |
| vysledek do 256 kB | vetsi objekt uz stejne nikdo dal nezpracuje |
| kod do 20 000 znaku | delsi uz neni prevod, ale aplikace |
| zadny stav mezi behy | stav, ktery prezije beh, je zdroj nejhur hledanych chyb |
Cim to neni. node:vm neni bezpecnostni hranice proti nekomu, kdo se chce
dostat ven. Je to izolace proti nehode a proti zacykleni. Skript pise spravce
te same firmy, tedy nekdo, kdo jeji data stejne vidi. Kdyby mel skripty psat
nekdo zvenku, patri to do samostatneho procesu s vlastnimi pravy.
Opraveno pri tom
- Casovy limit nepokryval samotny beh. Prvni verze pouzila
runInContextjen na vyrobu funkce a zavolala ji az potom zvenku -while (true) {}uvnitr ni zablokovalo proces navzdy. Zjisteno pri zkousce, ktera se zasekla. Kod se ted vola uvnitrrunInContext, takze limit plati. - Chyba z limitu nese prototyp z kontextu skriptu, takze
instanceof Errorna ni neplati. Pozna se podle textu. consolev novem kontextu existuje samo od sebe a psalo by do logu serveru. Odebrano.
2026-08-20 - prace nad celym modelem, ne nad plochym seznamem poli
Odesilatel posle cely model - objednavka ze Shoptetu ma zanoreni, ceny
v podobjektech a seznam polozek. Dosud se dal napojit jen plochy seznam
skalarnich poli, takze items[] neslo pouzit vubec.
Pridano
- Cesty v sablonach.
{{data.order.billingAddress.city}},{{data.order.items[0].name}},{{st_faktura.invoiceId}}. Hleda se nejdriv presny klic, teprve pak cesta - vystupy kroku se ukladaji i pod klic s teckou a presna shoda musi mit prednost. - Cele telo v kontextu. Krome pojmenovanych hodnot je k dispozici i telo v puvodnim tvaru, takze cesta funguje, aniz by to nekdo predem vypsal jako parametr. Deklarovane parametry maji prednost pri shode jmen.
- Ukazka tela u spoustece (
trigger.sample). Vlepi se telo z realneho volani a odvodi se z nej model, tedy seznam cest i s typy (src/data/model.ts). V krocich se cesty klikaji, misto aby se opisovaly, a kontrola vi, zedata.order.codeneni preklep.- Tlacitko Doplnit parametry pro podminky z hodnot v ukazce udela deklarovane parametry. Podminka se pta na parametr, ne na cestu.
- Krok Pro kazdou polozku (
foreach). Projde seznam v datech a za kazdou polozku vykona vnoreny podstrom. Uvnitr je{{item}}a{{index}}, po skonceni{{krok.results}}se seznamem vysledku.- Kazdy vysledek nese
index, celou puvodni polozku a vystupy kroku. Radek objednavky potrebuje jak ID z CRM, tak mnozstvi z puvodnich dat - kdyby se nesly obe, muselo by se to znovu parovat. - Strop je 200 polozek. Vic uz neni automatizace, ale zatez, kterou nikdo necekal, takze se beh zastavi a rekne to.
- Kdyz na ceste neni seznam, krok selze s tim, co tam misto nej je.
- Kazdy vysledek nese
- Prevod Za kazdou polozku seznamu v klikacim editoru mapovani. Engine ho
umel (
op: 'map'), ale sel napsat jen rucnim JSONem.
Overeno
Nad skutecnym modelem objednavky ze Shoptetu: 23 cest vcetne
data.order.items[].unitPrice.withoutVat, sablony s cestou i s indexem,
struktura predana jako JSON, neexistujici cesta jako prazdno s hlaskou v logu,
smycka nad dvema polozkami s posbiranymi vysledky.
Co to nemeni
Ploche odkazy {{callSid}} funguji dal presne jako driv - overuje se prvni
cast odkazu, takze u nich se nic nezmenilo. Existujici automatizace se nemusi
nijak upravovat.
2026-08-17 - log rekne, co zpusobilo jakou zmenu
Nalezeno na bezicim serveru: ticket TK-4946 mel 177 prichozich udalosti a 620 radku v logu, pritom se nestalo skoro nic. Zmereno proti behum ve fronte: ve stejnem okne vzniklo presne tolik behu, kolik prislo udalosti (22 a 22), kazdy s jednim pokusem. Fronta ani worker nic nenasobi - odesilatel poslal 177 POSTu. Nasi vinou bylo, ze to z historie neslo poznat.
Opraveno
- Data udalosti se konecne ukladaji.
payloadbyl u kazde udalosti prazdny, protoze krokticket/upsertukladal jen to, co si clovek vyplni v poli Data. Kdyz je prazdne, ulozi se to, cim beh zacal - u webhooku cele prijate telo. Prazdna udalost je horsi nez zadna: tvari se, ze se neco stalo, a nerekne co. - Shodna udalost se pocita, nezaklada dalsi radek. Kdyz prijde presne totez
co posledne, pricte se k pocitadlu (
repeats,lastAt) a v portalu se u radku ukaze "22x beze zmeny, naposledy ...". Ticket se pritom nemeni: neprepise seupdatedAtani se nerozesle zmena, takze duplikat nerozbliká dashboard a nespusti automatizaci navazanou na zmenu ticketu.- Zahodit duplikat nejde. Bez pocitadla by nikdo nezjistil, ze proti nam neco tluce ve smycce.
- Porovnava se jen s posledni udalosti. "Objednavka pripravena" muze legitimne prijit znovu za hodinu - to je novy fakt.
- Zmeny se radi pod udalost, ktera je zpusobila. Log uz mel strom
(
parentId), ale nikdo ho nepouzival. Zapisy behu se ted radi pod jeho radek udalosti, takze log se cte jako "prislo tohle -> zmenilo to tohle". - U udalosti stoji, kdo ji prinesl:
Prijata udalost: stav completed (automatizace TEST). Prvni otazka nad zmenenym ticketem je "kdo mi do toho sahl" awebhookna ni neodpovida. - Poznamka o stavu jen kdyz se stav zmenil. Predtim se u kazde udalosti
zapsalo
Stav zmenen z in-progress na in-progress, tedy tri radky logu na jednu zpravu, ktera nic nezmenila. Odtud 620 radku. - Popisek udalosti uz neni porad "Udalost". Bere se popisek, predmet, stav, externi ID - v tomhle poradi. Casova osa, kde je na kazdem radku totez, nerika nic.
- Opakovani nezapisuje radek do logu vubec. Krok muze rict
quiet, cimz se jeho radek do logu ticketu nepise - pocet opakovani je videt u prichozi udalosti, coz je jedno cislo misto osmdesati radku. - Ticket se rodi s poslanym stavem. Predtim vznikl s vychozim
Novýa hned se prepsal, takze v logu stalostav Nový -> completedu ticketu, ktery v tom stavu nikdy nebyl. Odtud i toNový, co bylo videt ve widgetu. - Typograficke uvozovky z popisku v logu pryc, plus ctyri AI znaky, ktere
zbyvaly v kodu (
web/src/data/products.ts,References.tsx,automationStore.ts).
runsToday konecne znamena dnes
Bylo to pocitadlo od zalozeni automatizace, jen se jmenovalo "dnes" - na serveru ukazovalo 373 za automatizaci, ktera bezi tri mesice.
- Automatizace si drzi behy po dnech (
days, poslednich 14 dni). Po pulnoci je "dnes" nula, dokud opravdu neco nebezi. - Vedle toho
runsYesterdayarunsTotal. Celkovy pocet se pocita od zavedeni historie po dnech, protoze puvodni citac se den ode dne nedelil a rozpocitat ho zpetne neni z ceho. - Uspesnost se pocita z dnesnich behu. Kdyz dnes zadny nebyl, bere se posledni den, kdy byly - nula procent u automatizace, ktera dnes nemela co delat, by vypadala jako porucha.
- V seznamu je pod dnesnim cislem vcerejsek, na detailu dnes, vcera i celkem.
2026-08-17 - prevzeti ticketu ze skupiny a pozvanky do firmy
Pridano
- Prevzeti ticketu.
POST /tickets/:id/claim. Kdo je ve skupine, ktera ma ticket u sebe, si ho vezme sam. Prace se nerozdava shora, lidi si ji beru podle toho, kdo ma cas.- Vzit ticket, ktery uz nekdo resi, je neco jineho: to je prehozeni a chce
to pravo
ticket.assign.others. - Ticket bez resitele si vezme kdokoli, kdo je vedeny jako resitel.
- Vzit ticket, ktery uz nekdo resi, je neco jineho: to je prehozeni a chce
to pravo
- Krok
ticket/assign-group(Predat skupine) s prepinacem Priradit rovnou nejvolnejsimu. Prazdne nebo Ne = ticket zustane ve fronte skupiny. Prepinac je na kroku automatizace, ne na skupine: tataz skupina potrebuje u havarie okamzite prideleni a u bezneho dotazu ne. - Pozvanky do firmy. Spravce vytvori odkaz s nahodnym kodem
(
/pozvanka/:kod), posle ho, jak chce. Kdo ho otevre, vyplni jmeno a heslo a je uvnitr.- Heslo se nikdy neposila. Nastavuje si ho sam clovek az za odkazem.
- Kdyz uz ucet ma, zada k nemu svoje heslo a jen se pripoji k dalsi firme. Bez overeni hesla by kdokoliv s odkazem pripojil cizi adresu ke sve firme a videl by jeji data.
- Pozvanka plati tyden, da se zrusit a po pouziti prestane platit sama.
- Volitelne z cloveka rovnou udela resitele. Ucetni muze mit pristup do portalu, aniz by kdy resila ticket, proto se to pta.
Zmeneno
- Resitele, skupiny a pozvanky jsou na strance Lide, ne v nastaveni. Pozvat kolegu je bezna denni prace, ne nastaveni portalu - dokud to bylo schovane v nastaveni, nikdo to nenasel.
- Cleny skupiny se vybiraji klikanim ze seznamu lidi. Predtim se opisovala
ID
ppl_xxxoddelena carkou, coz je preklep cekajici na sve misto.
Nove soubory
| Soubor | Co dela |
|---|---|
src/data/invites.ts |
Entita pozvanky, kod, platnost. |
src/routes/invites.ts |
Verejne cesty (prohlednuti a prijeti) a sprava. |
web/src/pages/Invite.tsx |
Stranka za odkazem: jmeno, e-mail, heslo. |
web/src/components/dashboard/InvitePanel.tsx |
Sprava pozvanek v zalozce Lide. |
2026-08-13 - vyrizeno je vyslovny priznak, ne hadani ze stavu
Zmeneno
Ticket.closedse nastavuje vyslovne. Predchozi verze ho odvozovala ze jmena stavu (completed,vyreseno, ...), coz nikdo nechtel a hlavne to uhodne spatne pokazde, kdyz si nekdo pojmenuje stavy po svem.TicketType.closedStatuseszruseno. Byla to tatáz obchazka o uroven vys.- Kroky
ticket/upsertaticket/set-statusmaji vstup Vyrizeny (ano/ne/prazdne). Prazdne znamena nemenit - jinak by kazda zmena textu stavu mimochodem otevrela vyrizeny ticket. POST /tickets/:id/statusprijimaclosedjako nepovinny bool.- Podminka se muze zeptat na
closed, takze jde napsat "kdyz je vyrizeny".
Overeno
8 kontrol: ticket se stavem completed neni automaticky vyrizeny, dokud
to nekdo nerekne. Automatizace s podminkou status = completed zabere na
ticketu, ktery do toho stavu prejde, prida stitek a nastavi vyrizeno. Na uz
existujici tickety nesahne, dokud se s nimi neco nestane - spousti ji udalost,
ne stav.
2026-08-13 - stav ticketu je volny retezec, ciselnik pryc
Zmeneno
Ticket.statusje volny retezec. Ciselniknew | open | waiting | resolvedje pryc. Tickety chodi z cizich aplikaci, ktere maji svoje stavy - voicebot posilaringingacompleted, e-shoppripraveno 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: podleTicketType.closedStatuses, a kdyz je typ nema, podle bezneho pojmenovani (vyreseno,hotovo,completed,closed). stagezruseno. 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
ringingdobre 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/upsertumi 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/assignnemel 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
assignedaknownCustomer, aby slo napsat podminku "uz je prirazeny, nesahej na to". Bez toho nemela z ceho vychazet.
Zmeneno
ticket/upsertprijimastatus. 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.
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 poslevoicebotIda 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:
impactcte klient a je srozumitelny,detailcte admin a je v nem cely beh, ktery krok selhal, cele hlaseni a data na vstupu.detailse 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
ttlSeca jde vynutit nacteni znovu.
Opraveno
pathaintervalSecu 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:
- 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.
- 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 presrunScripta 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: 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.
validateLayoutznala 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.
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.externalIdna 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 by3a"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,resolvedByIdareopenCountna 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
createTicketbraltypeId,fields,tagsiassigneeGroupId, 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 a vsechna data se ukladaji. Rejstrik novych funkci a komponent je v 15-rejstrik-funkci.md.
Pridano - obecne vrstvy
src/data/store/: jedno rozhraniEntityStore<T>a dve implementace, soubor nebo pamet (local.ts) a Postgres nad tabulkourecords(postgres.ts). Volajici nepozna, ktera bezi. Nova entita znamena jedendefineStorea jeden radek vbootstrap.ts.store/cached.ts(withCache) pro konfiguracni entity ctene pri kazdem requestu astore/mirror.ts(withMirror) pro provozni data, ktera se meni v pameti a po zmene se cela zapisuji. Dva pomocniky, ne osm skoro stejnych souboru.putv 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 (.tmpa prejmenovani), slucovani zapisu a dokonceni rozepsaneho zapisu priSIGTERM. 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. Nahrazujememory.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_DIRv konfiguraci,data/v.gitignorea.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.
Pridano
pgjako zavislost, pool vsrc/db/pool.tsvcetne transakci adbFor(tenantId)jako sev pro budouci oddelenou databazi jednoho klienta.- Migrace ze souboru
src/db/migrations/*.sql, pousti se pri startu podpg_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, vsrc/data/connectorStore.ts. /health/readys pingem do databaze./healthna databazi zamerne nezavisi: kratky vypadek DB by jinak vedl k restartovani containeru.GET /api/dashboard/storagea 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_URLneboSECRETS_KEYse 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.
Pridano
- Kroky si predavaji i cele struktury.
FieldTypemaobjectalist. 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 nactx.utiljakoget,applyRulesafillJson.- Dva rezimy transformace:
transform.map-fields(pole na pole s prevody) atransform.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
mappro 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 ignorujeautocomplete="off"a dosazuje ulozene prihlaseni. Uzivatel pak videl jednu hodnotu, React drzel jinou, a ulozilo se to, co drzel React, tedy nic. Resi toautocompletenew-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.
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 nesegeneral,appId,visibility,credentialsaverifyPath. Kategorieobecnesdruzuje 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, jenfilledamissing.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/sluzbya/dashboard/konektoryvcetne formularu udaju. - Endpointy
/api/dashboard/servicesa CRUD/api/dashboard/connectorsvcetne 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
availableneboplanned. - Prejmenovani napric kodem:
ConnectornaService,FlowStep.connectorIdnaserviceId,GET /connectorsnaGET /services,connectorIconsnaserviceIcons, 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.
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.
ctxpredavany skriptu:httpnad adresou napojeni,util,log,config,idempotencyKey,failaretry. 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/testa/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. ConnectorOperationmaimplementationascriptId. Katalog v portalu operace se skriptem oznacuje ikonou.ApiErrorna klientovi nese cele telo odpovedi a umi z nej vytahnoutissues.- 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.
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 a 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.
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 spoustececreated,unknown-customer,assigned,status-changeda akceassign,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
meaunassigned), stav, kanal. providedFieldsv katalogu konektoru: parametry, ktere spoustec predava sam.- Udalost
ticket.assignedna sbernici i v portalu. - Endpointy
/api/dashboard/people,/tickets/workload,/tickets/:ida POST varianty pro assign, status a comment. Vse ve Swaggeru.
Zmeneno
Ticketma misto volnehorequesterstrukturovanehocustomers nullableidfirmy v CRM, k tomuchannel,assigneejako odkaz na cloveka aautomationId.customer.id === nullje 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.bodyaTicket.sourceRef. Predmet je shrnuti, telo je cely text pozadavku.bodyvystaveno i ve spoustecichcreatedaunknown-customer, takze na obsah ticketu jde udelat podminka v navazne automatizaci.- Nastavitelna pole akci (
OperationFieldaFlowStep.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
inputsto 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.
outputFieldsv 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.tspocita, 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
facebookainstagramu 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 splnenoneprepise 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.
- Tenant jako hranice viditelnosti.
tenantIdna ticketu, resiteli i automatizaci. - Uzivatel muze patrit do vic firem, v kazde s jinou roli (
memberships). Pristup napric firmami je zvlast jakoplatformAdmin. - Tri pohledy na tickety:
all,tenant,mine. Admin mezi nimi prepina, vcetne vyberu firmy. src/data/access.tsjako jedine misto, kde se rozhoduje o pravech.GET /api/dashboard/accessrekne 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.
requireRolenahrazenrequirePlatformAdmin. Prava uvnitr firmy resiaccess.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.
- 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.
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
/docsa OpenAPI definice na/openapi.json. - Dokumentace ve slozce
documentation/. .gitignore, ktery drzinode_modulesadistmimo repozitar.
Zmeneno oproti sablone
- Aplikace prepnuta na ESM (
"type": "module") amodule: 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.0beze zmeny.
Reseni reverse proxy
ROOT_PATHse 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.htmlznacku<base>awindow.__BASE_PATH__, aby SPA nasla soubory i na vnorenych cestach. - OpenAPI
serversobsahuje prefix, takze Swagger Try it out vola spravnou adresu. - Router ma
strict: true, jinak by se presmerovani/docsna/docs/zacyklilo.
Overeno lokalne
S ROOT_PATH=/apps/csbot-prototype:
/apps/csbot-prototype/healthi/healthvraci 200,/apps/csbot-prototype/docspresmeruje na/docs/, ta vraci Swagger UI,swagger-ui.cssse nacte pres prefix,- OpenAPI
serversobsahuje/apps/csbot-prototype, index.htmlna vnorene ceste obsahuje spravny<base>,- prihlaseni pres prefix vraci token,
- neexistujici cesta pod
/apivraci 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.