104ae36783b2edf0c643012dfdf3b3d3e057bc04
60
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
104ae36783 |
Revize projektu: prava, vykon, runtime, portal a ARES
Prava a bezpecnost: spravce firmy uz nemuze nastavit priznak spravce platformy ani clenstvi v cizi firme; pozvanky, konektory a automatizace kontroluji sve pravo; cizi firma v query je 404; zivy stream posila udalosti jen firmam, kterych se tykaji; akce nad ticketem maji kontrolu prava za firmu ticketu a strop viditelnosti; tokeny se nelogujou; limit pokusu na prihlaseni, kontakt a pozvanky; bezpecnostni hlavicky; zachyceni chyb v async handlerech; timing-safe porovnani tokenu. Vykon: audit neskenuje celou kolekci pri kazdem zapisu a konecne maze firemni zaznamy; ticket se uklada jednou misto trikrat; zapisy do Postgresu jsou serializovane podle ID; prava se pocitaji jednou na request; widgety nacitaji tickety jednou; strankovani seznamu; worker je pool misto kol; na webu udalost ze streamu neodmontuje stranku, dotazy maji spolecny debounce a cache, ciselniky drzi typovany sklad. Runtime: opakuji se jen chyby oznacene retryable; smycka nenarazi na strop 50 kroku (novy strop 1000 akci); podminka nad datem funguje; vystup MCP nastroje neprepisuje spoustec; sandbox skriptu firmy nejde opustit; MCP session id se drzi mezi volanimi; incident z kroku patri firme; jedno rozhodnuti o rezimu uloziste; snapshot neprepise soubor po chybe cteni. Refaktory: sdilene typy API v src/shared (web nic nekopiruje, osm rozjetych tvaru sjednoceno); spolecny modul net/guard pro volani ven; formularova vrstva ui/form; rozdeleni Connectors a FlowCanvas; jeden helper pro firmu z query, validaci a CRUD udalosti; pomucky ctx.util pro skripty konektoru; i18n verejneho webu vcetne anglictiny. Nova funkce: zalozeni firmy z registru ARES v Nastaveni (IC nebo nazev, dotazeni IC, DIC, sidla a pravni formy, vyber soucasnych statutarnich zastupcu a prokury, ucty spravce firmy s nahradnim e-mailem IC-poradi@placeholder.cz). Dokumentace: zaznam v 99-zmeny.md a aktualizace 15 dalsich dokumentu. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
0c405ea55a |
Otevrena stranka sekala prehravani videa
Pri otevrenem portalu zacalo vedle nej sekat prehravani videa, po zavreni bylo hned dobre. Neslo o smycku v kodu: zivy prenos udalosti je SSE, jedno spojeni na celou aplikaci, a setTimeout je v nem jen na odstup pri znovupripojeni. Byly to tri veci. Rozmazavani plne barvy. `.glass`, podklad skoro kazdeho panelu, mel backdrop-filter blur(14px). Jenze pod kartami je plna barva (bg-ink-950 na dashboardu), takze se rozmazavala jednolita plocha: zadny rozdil videt nebyl a kazda karta si za to drzela vlastni rozmazanou vrstvu ve skladaci. V kodu je glass na 58 mistech, na jedne obrazovce jich je snadno dvacet. Rozmazani zustava na peti mistech, kde pod nim neco opravdu projizdi: lepici hlavicky, podklad modalu, prekryv menu na mobilu. Nekonecne animace nad velkymi rozmazanymi plochami. Prihlaseni a verejny web mely zare o velikosti 26 az 34 rem s blur(120px), kterym se donekonecna animovala pruhlednost - to znamena prepocitavat obrovskou vrstvu sedesatkrat za sekundu, i kdyz se na strance nic nedeje. Zare zustaly, animace zmizela. Pribylo prefers-reduced-motion. Kazda udalost prekreslila cely dashboard. EventStreamProvider mel v tomtez kontextu stav spojeni, odber i seznam poslednich udalosti. Seznam se meni pri kazde udalosti, takze se menila cela hodnota kontextu a prekreslil se kazdy, kdo si jen zaridil odber - tedy vsechny dotazy s refetchOn na strance, i kdyz o ten typ udalosti nestaly. Pri behu automatizace se cely dashboard prekresloval nekolikrat za sekundu, a s nim dvacet rozmazanych vrstev. Kontext je proto rozdeleny: useEventStream da stav a odber, useEventLog seznam udalosti. Filtr sekci nefiltroval. Seznam ticketu sklada adresu dotazu v useMemo, ale v zavislostech chybely groupId, typeId, tag a stage - adresa se po prepnuti sekce neprepocitala a data se nenacetla znovu. Driv to nevadilo, byly to filtry nastavitelne jen odkazem, ktere se po nacteni nemenily. Se zalozkou "Vsechny tickety" a jejim vyberem sekce je to chyba, ktera je videt. Overeno: groupId=grp_servicedesk vrati jeden ticket, groupId=none zbyle dva. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9f4b8c0596 |
Podminka se muze ptat na vic veci naraz
Podminka byla prave jedna otazka. Slozitejsi vetveni se muselo skladat
z vnorenych podminek, takze "vysledek dorazil" a v nem "vysledek je X" byly dve
urovne stromu misto jedne vety. U tri hodnot, ktere maji dopadnout stejne, to
byly tri urovne.
Model: rules (otazky) a match ('all' = a zaroven, 'any' = nebo). Stara podoba
fieldId/operator/value primo na kroku se dal cte, prevadi ji rulesOf - jedine
misto, kde se to deje. Kdyby se fieldId cetlo primo, krok ulozeny driv by po
zmene modelu prisel o svou otazku a vetvil by vzdycky stejne, tise a bez chyby.
Zapisuje se uz vzdycky rules.
fieldId je v typu nepovinne schvalne: prekladac tim ukazal vsech pet mist,
ktera podminku ctou (vyhodnoceni, obe hlasky do logu, kontrola pri ukladani,
kontrola nedodelku).
Proc jedna uroven a ne vyrazy se zavorkami: dve treti podminek jsou "vsechny
tohle" nebo "cokoliv z tohohle". Zavorky by v rozhrani znamenaly editor vyrazu,
ktery uz nikdo neuklika, a textovy zapis by navic zahodil odkaz na parametr pres
ID - diky nemu prejmenovani parametru podminku nerozbije a builder umi nabidnout
jen operatory, ktere na dany typ sedi. Az se ukaze, ze jedna uroven nestaci,
da se textovy zapis pridat nad tentyz vyhodnocovac. Opacne to nejde.
V builderu radek na otazku, tlacitko "Přidat otázku" a od druhe otazky prepinac
"sedí všechny" / "sedí aspoň jedna". Posledni otazka nejde smazat: podminka bez
otazky by tise nevetvila, runtime ji povazuje za nesplnenou a zaloguje to.
Radek podminky v logu nese vsechny otazky i s tim, ktera rozhodla - bez rozpadu
by u spojene podminky bylo videt jen "nesplneno".
Overeno na bezici instanci, strom se dvema spojenymi podminkami a peti volanimi:
vysledek nedorazil (nic), Chybějící informace (prirazeno), Přesměrování
a Mimo téma (zavreno pres any), Něco jiného (nic). Strom ulozeny ve stare
podobe se nacte a ulozi beze zmeny.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
1532ea0af7 |
Automatizace zavirala tickety uz pri zvoneni
Na instanci nebyl ani jeden nevyrizeny ticket: vsech 131 melo closed true,
takze dlazdice "Moje tickety" i "Fronta bez resitele" ukazovaly nulu. Widget
pocital spravne, spatna byla data.
Strom se ptal `result neq "Chybějící informace"` a ve vetvi ANO ticket zaviral.
Jenze jeden hovor posle vic zprav a ta prvni jen ohlasi, ze zacal: data je null,
takze {{result}} je prazdne. evaluate prevadi chybejici hodnotu na prazdny
retezec, a prazdno se opravdu nerovna "Chybějící informace" - podminka tedy
sedla a ticket se zavrel uz pri zvoneni.
Odtud i druha vec: prirazovaly se i tickety, ktere nemely. Jedna zprava hovoru
mela result "Chybějící informace", takze ticket sel na servicedesk a na cloveka.
Dalsi zprava tehoz hovoru prinesla "Přesměrování" a ticket zavrela, ale resitele
uz neodebrala. Z dvaceti ticketu prirazenych jednomu cloveku jich melo
"Chybějící informace" jen deset.
Opraveny strom se nejdriv pta, jestli vysledek vubec prisel, a teprve pak
kladne, jestli je to "Chybějící informace":
result isNotEmpty
ANO result eq "Chybějící informace"
ANO priorita vysoka, sekce Servicedesk, prirazeni nejvolnejsimu
NE zavrit
NE nic, vysledek jeste nedorazil
Puvodni neq znamenalo "vsechno ostatni vcetne toho, co jeste nevime". To je
u nepovinneho parametru, ktery dorazi az pozdejsi zpravou, past.
Radek podminky v logu rikal jen "splneno". Ted nese i to, s cim se porovnavalo,
a rozlisuje nedorazilo od prazdneho - prvni radek je presne ta informace, ktera
chybela:
Podmínka: result isNotEmpty: nesplněno | result = nedorazilo
Podmínka: result eq Chybějící informace | result = "Přesměrování", porovnáno s "Chybějící informace"
Overeno prehranim celeho hovoru na bezici instanci: prvni zprava bez dat ticket
zalozi a nechá otevreny, druha s "Chybějící informace" ho preda a priradi, treti
s "Přesměrování" ho zavre. Pred opravou byl vyrizeny uz po prvni zprave.
Zustava k rozhodnuti: pri prehodnoceni hovoru resitel na zavrenem ticketu
zustane. Do "Mych ticketu" nespadne, ty ukazuji jen nevyrizene, ale ve vsech
ticketech u nej to jmeno stoji.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
f7ad429b3a |
Nazev znacky uz neni v kodu natvrdo
Server mel "Automia" napsanou primo v titulku Swaggeru a v OpenAPI - zbytek po prejmenovani, ktere probehlo jen na klientovi. Prejmenovat produkt tedy znamenalo hledat retezec po souborech. Dve promenne, a je to zamer: - server cte BRAND_NAME za behu, vychozi WorkNuke - klient cte VITE_BRAND_NAME pri buildu, vychozi tataz Klient je staticky soubor, takze runtime promenne containeru do prohlizece nedosahnou - proto se u nej dosazuje pri buildu. Obe maji tutéž vychozi hodnotu, takze bez nastaveni cehokoliv sedi. Zbytek firemnich udaju (claim, kontakty, adresa) zustava v brand.ts. Do promennych to nepatri, meni se to jednou za rok a env by z toho udelalo deset promennych, ktere nikdo nenastavi. Co se timhle nemeni: firma Automia v ukazkovych datech. Pozvanka ukazuje jmeno firmy, do ktere zve, a ta se v seedu jmenuje Automia - neni to zbytek po prejmenovani, je to zaznam zakaznika. Overeno: s BRAND_NAME=ZkouskaZnacky ma OpenAPI titulek "ZkouskaZnacky - portal a API" a Swagger "ZkouskaZnacky API". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5984b1cb9b |
Oprava: "Interni chyba serveru" pri zalozeni ticketu
Formular noveho ticketu koncil na 500 uz pri vyplnenem samotnem predmetu.
apiFetch prevadi telo na JSON sam:
body: body !== undefined ? JSON.stringify(body) : undefined
Dialog mu ho ale predaval uz prevedene, tedy body: JSON.stringify({...}).
Druhy prevod z objektu udelal retezec a na server dorazilo "{\"subject\":...}".
express.json je ve vychozim nastaveni strict, takze retezec na nejvyssi urovni
odmitne a vyhodi vyjimku. Tataz chyba byla i ve dvou mistech helpdesku,
u zalozeni pozadavku a u komentare.
Druha polovina je hlaska. Vyjimka z parseru tela propadla do centralniho error
handleru, ktery z ni udelal 500 "Interni chyba serveru" - hlasku, ktera rika,
ze je neco spatne u nas, a posila cloveka hledat na spatnou stranu, pritom slo
o spatne polozeny dotaz. Error handler proto chyby parseru tela rozpozna
(status 400 a type zacinajici entity.) a vraci 400 s vetou o neplatnem JSONu.
Ostatni chyby zustavaji 500, jak byly.
Overeno na bezici instanci: telo zakodovane dvakrat vraci 400 s tou vetou,
opravene telo jen s predmetem vraci ticket.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
79c9a729f6 |
Builder nabizi jen to, co jde zavolat
V nabidce kroku byly vsechny sluzby katalogu, i ty, ke kterym firma nema napojeni. Slo tedy vybrat Raynet CRM bez konektoru a postavit strom, ktery pri prvnim behu spadne na chybejicich udajich - a to se pozna az za tyden, kdyz prijde prvni ostra udalost. Nabizi se sluzba, ktera je obecna, nebo k ni firma ma napojeni. Obecne jsou ty, co se bez konektoru obejdou: webhook, casovac, rucni spusteni, formular, incident, ticket, HTTP, transformace, pauza a log. Priznak general uz existoval, jen ho nikdo nepouzil na filtrovani nabidky. Katalog se kvuli tomu nefiltruje, jen se oznacuje: GET /api/dashboard/services prida ke kazde sluzbe connected. Sluzba z odpovedi nemizi, protoze log ticketu a detail akce podle katalogu prekladaji ID operaci na jmena - kdyby zmizela, zustalo by v uz zapsanem radku hole ID. Filtruje az builder. Pod nabidkou je veta, kolik sluzeb ceka na napojeni, aby to nevypadalo, ze neexistuji. Soukroma sluzba uz neni videt cizi firme. canSeeService vracelo spravci platformy true driv, nez se vubec podivalo na viditelnost, takze zakazkova integrace omezena na jednoho klienta se ukazovala i po prepnuti do jine firmy a spravce si ji mohl vybrat do jeji automatizace. Nove rozhoduje firma, ne clovek: kdyz je vybrana, plati jeji seznam, bez ni spravce platformy spravuje katalog a vidi vsechno. Zaroven se konecne pouziva tenantHasService, tedy zpristupneni sluzby firme pres nastaveni - dosud to bylo pole, ktere nikdo necetl. Overeno na bezici instanci: Polstryn SAP je pro tnt_logitrans v katalogu, pro tnt_automia uz ne, a to i pro spravce platformy. V nabidce builderu pro tnt_automia zbylo deset obecnych sluzeb plus iDoklad, na ktery firma napojeni ma. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e701bc0e2c |
Tickety maji zalozky, fronta umi prirazovat
Seznam ticketu mel dva prepinace, "Moje tickety" a "Ve fronte", schovane mezi ostatnimi filtry - splyvaly s nimi a vypadaly jako dalsi dva chipy. Jenze "co mam u sebe", "co jeste nikdo nema" a "co je ve firme" nejsou filtry, jsou to tri ruzne prace a kazda chce jinou tabulku. Tri zalozky: - Moje tickety, pro toho, kdo je veden jako resitel. Bez sloupce Resi, byl by tam porad on - Nezarazene, pro kazdeho. Radky s rychlym prirazenim - Vsechny tickety, jen pro toho, kdo vidi i cizi. Sloupec Resi a filtr na sekce Sloupec Resi je jen ve Vsech: v Mych by ve vsech radcich stalo tyz jmeno a ve fronte je z definice prazdny. Az v prehledu vsech ma smysl videt, kdo co ma u sebe. Zalozka Vsechny se nenabizi tomu, kdo vidi jen svoje - byl by to tentyz seznam. Rika to Access.seesOthers, a Access.visibleGroups k tomu prida sekce, ktere smi filtrovat: kdo vidi celou firmu vsechny, vedouci jen ty, ktere vede. Nova QuickAssign: u kazdeho ticketu ve fronte tlacitko Vzit si a dve roletky, komu a do ktere sekce. Prirazeni je ve fronte hlavni prace, ne jedna z mnoha veci na detailu - otevirat kvuli tomu kazdy ticket znamena u dvaceti ticketech ctyricet kliknuti navic. Dve roletky, ne jedna: clovek a sekce jsou dve ruzna rozhodnuti. Kdo nesmi rozdavat praci ostatnim, vidi jen Vzit si. Endpointy uz existovaly, nova je jen cesta k nim. Vychozi zalozka se odvodi z adresy: odkaz z widgetu nese assignee, takze unassigned otevre frontu a konkretni resitel nebo sekce pohled Vsechny. Bez toho by clovek prisel z dlazdice "fronta bez resitele" a koukal na svoje. Do navrhu 25 zapsano, ze hledani ma zapadnout do zalozky, ve ktere clovek stoji, ne ji obejit, a vysledek se ma vracet do te same zalozky. Overeno na bezici instanci: spravce platformy ma zalozku Vsechny a vsechny sekce ve filtru, Vomacka jako vedouci Servicedesku jen svoji sekci, Kriz jako radovy clen zalozku Vsechny nema. A cely pohyb ticketu frontou: admin preda TK-4819 do sekce, Kriz ho vidi ve fronte, vezme si ho a objevi se mu v Mych ticketech. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
52999a961b |
Nezarazene vidi kazdy, a dlazdice "Moje tickety"
Dve veci, ktere ze stropu viditelnosti vypadly. Nezarazeny ticket je ve stropu vzdycky. Prvni verze ho schovavala: kdo nevidel na celou firmu, nevidel ticket bez resitele a bez skupiny. Byla to diera v provozu - prichozi ticket, ktery jeste nikdo nesmeroval, nepatri do zadne sekce, takze by ho nevidel nikdo krome vedeni a nikdo by si ho nevzal. Fronta je spolecna, prave proto je to fronta. Jakmile si ho nekdo vezme, plati strop jako u kazdeho jineho. Dlazdice "Moje tickety" a "Fronta bez resitele" byly v navrhu od zacatku a nikdy se neudelaly. Presne jak navrh rikal: zaznam v katalogu a zdroj v builtinSources, zadna nova komponenta, data pocita tataz cesta jako u vykonu resitelu. Tri veci, ktere si to vyzadalo: - closed ve WidgetTicketFilter. Bez nej se vyrizene tickety nedaly odfiltrovat: stav je volny retezec a vyjmenovat vsechny podoby slova "hotovo" se neda. Prospeje to i vlastnim widgetum, dosud neslo postavit "otevrene tickety podle typu" - ResolvedScope.personId se plni vzdycky, ne jen u pohledu mine. Prehled se pta v pohledu tenant, takze filtr me nemel co dosadit a dlazdice vracela prazdno. Kdo je to "ja", na pohledu nezalezi - kdo neni veden jako resitel, tomu se "Moje tickety" nenabidnou. Ticket se prirazuje resiteli, ne uctu Vychozi rozlozeni ma nahore to, co clovek muze udelat, teprve pod tim cisla. Graf behu z vychozi sady ven, je to nejmene srozumitelna dlazdice pro noveho cloveka a zabira celou sirku. V katalogu zustava. Zmena se projevi jen tem, kdo si dashboard jeste neupravili. Overeno na bezici instanci: Kriz jako radovy clen sekce vidi ve svych ticketech TK-4820, ve fronte nezarazeny TK-4819 a v seznamu oba. Pred opravou personId vracela prvni dlazdice prazdno. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2a310137a2 |
Detail ticketu ma rozradovace, ne tri karty pod sebou
Navrh v documentation/25 mel detail jako jednu slozku s rozradovaci, ale v aplikaci zustaly tri karty pod sebou: obsah, prichozi udalosti a log prubehu. Detail byl tri obrazovky dlouhy a to podstatne, tedy obsah pozadavku, se v tom ztracelo. Ted jsou to listy jedne karty: - Pozadavek - obsah a pole na komentar, vychozi - Log prubehu - co delala automatizace a co ji sluzby vratily - Udalosti - co presne prislo zvenku - Vlastni pole - hodnoty poli typu ticketu U kazdeho je pocet, takze je videt, jestli se tam vubec vyplati kliknout. Vlastni pole se nenabizeji, kdyz zadna nejsou - prazdny rozradovac je jen dalsi vec k prehlednuti. Vychozi je vzdycky Pozadavek: to je duvod, proc clovek ticket otevrel. Log a udalosti jsou hloubkova data, na ta se sahá, az kdyz neco nesedi. Vlastni pole se do te doby nedala zobrazit vubec, i kdyz je ticket nesl - to je ten ctvrty rozradovac. Kresli je tatáz tabulka jako obsah ticketu (DataTable v TicketBody.tsx), takze se struktura vsude vypisuje stejne. Rozradovace pouzivaji tentyz vzhled zalozek jako zbytek portalu, ne tvar slozky z navrhu. Druhy styl zalozek na jedne obrazovce je presne ten drift, ktery jsme uklizeli u vstupnich poli. Overeno na bezici instanci: ticket z automatizace ma 5 kroku logu, 2 udalosti a zadna vlastni pole, takze se ctvrty rozradovac skryje. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6bcd7f845c |
Hierarchie firmy: kdo co vidi, a zalozka Firma
Pohled na celou firmu dostal kazdy, kdo do ni patril: scopes.push('tenant')
se v access.ts neptalo na nic. mine byl dobrovolny filtr, ne strop, takze
resitel s roli agent poslal ?scope=tenant a dostal cely provoz.
Rozdil, na kterem to ted stoji: pohled je co chci videt, strop je co vubec smim
videt. Existoval jen pohled a klientovi se veril.
Model:
- Membership.seesAllTenant - vidi cely provoz firmy. Postaveni uctu ve firme,
ne vlastnost resitele: clovek muze byt ve dvou firmach jednou reditel
a jednou brigadnik
- PersonGroup.members je { personId, seesAll } misto holeho personIds. seesAll
je vedouci sekce. Priznak visi na clenstvi, ne na cloveku a ne na skupine -
diky tomu muze byt clovek v peti sekcich a jen ve dvou videt vsechno, coz
role rict neumi, ta je jedna na celou firmu
- stara podoba personIds se dal cte, prevadi ji groupMembers
Vypocet stropu (visibilityFor), sjednoceni ne prunik: spravce platformy nebo
seesAllTenant vidi celou firmu, jinak svoje tickety plus vse ze sekci, kde ma
zaskrtnuto, plus jejich fronta. U zaznamu bez priznaku rozhoduje pravo
ticket.assign.others - kdo smel prehazovat cizi praci, uz stejne cely provoz
videl, takze se mu nic nebere.
Vynuceni:
- strop je povinna soucast TicketFilter, stejne jako tenantIds. Nepovinny filtr
na prava je filtr, ktery jednou nekde chybi - takhle prekladac ukazal vsech
trinact mist, ktera ho jeste nemela
- getWorkload a getAgentStats uz nesahaji do pole ticketu primo, jdou pres
listTickets. Driv obchazely kazde omezeni viditelnosti
- getTicket kontroluje strop i u jednoho ticketu, bez toho by stacilo znat ID
- detail a prevzeti pocitaji strop za firmu ticketu, ne za prave prepnutou
Helpdesk se ridi toutez hierarchii, jen "moje" znamena, co jsem zalozil -
zadavatel pozadavek nikdy nema prirazeny. Ticket proto nese createdById.
Zalozka Firma: pod jednim mistem Prehled, Resitele, Sekce, Role a prava, Typy
ticketu a Pozvanky. V Nastaveni zustal Muj ucet a platformni veci. Role a Typy
jsou samostatne komponenty, sekce maji vlastni panel - u kazdeho clenstvi je
prepinac a radek na cloveka obecny EntityAdmin neumi.
Obsah ticketu se cte i v helpdesku, v seznamu jako jednoradkovy nahled.
Overeno na bezici instanci se tremi ucty nad tymiz daty: spravce platformy vidi
vsechny tri tickety vcetne neprirazeneho, Vomacka jako vedouci Servicedesku dva
(tickety obou clenu sekce), Kriz jako radovy clen jeden, jen svuj. Adresa cizho
ticketu vraci 404, vytizeni tymu ukazuje jen viditelne a po zaskrtnuti priznaku
se rozsah zmeni hned.
Co z toho plyne: neprirazeny ticket bez skupiny nevidi nikdo krome toho, kdo
vidi celou firmu. Prichozi praci musi nekdo smerovat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
a6722f9379 |
Obsah ticketu se na detailu cte, ne jen vypisuje
body je jeden retezec, ale co v nem stoji, urcuje ten, kdo ho naplnil. Krok
"Zalozit nebo doplnit ticket" dosadi {{data}} a sablona objekt prevede na JSON,
takze v obsahu skoncila slozena zavorka vypsana jako odstavec. Necitelne, i kdyz
jsou v ni presne ty udaje, kvuli kterym ticket vznikl.
Nova web/src/components/dashboard/TicketBody.tsx telo prectе:
- veta od cloveka -> text tak, jak prisel
- cely retezec je JSON -> tabulka klic a hodnota
- seznam objektu -> tabulka se sloupci podle klicu
- text a za nim JSON -> odstavec a pod nim tabulka
- cokoliv, co se neprecte -> text tak, jak prisel
Ctvrta radka je bezny pripad, sablona {{voicebotId}}, {{data}} da presne tohle.
Deli se az od prvni zavorky, od ktere je zbytek platny JSON - bez te druhe
podminky by veta s jednou slozenou zavorkou skoncila rozpulena.
Zanoreni se kresli o uroven niz jako vnorena tabulka, hloubeji uz jako JSON:
tabulka v tabulce v tabulce se necte a odesilatele umi zanorit data hluboko.
Nic se nezahazuje, neprecteny obsah se ukaze jako puvodni text.
Overeno na osmi tvarech tela vcetne toho, ktery na instanci opravdu vznika,
vety se slozenou zavorkou a nedopsaneho JSONu.
Vedle toho zdroje vizualniho navrhu detailu ticketu v design/. Sestavene platno
je v gitignore, je to 2,5 MB generovaneho souboru, ktery vznikne znovu ze
zdroju vedle nej.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
40875439e4 |
U posledniho volani je videt, co presne prislo
Prvni verze seznamu poslednich volani ukazovala cas a duvod odmitnuti, ale ne data. To je k nicemu: "parametr data ma mit typ text" nerekne, co tedy prislo, a bez toho zbyva hadat. Argument, ze se telo nema drzet kvuli zakaznickym datum, navic neobstal. Tickety si cela prijata tela u udalosti drzi uz davno, takze si to jedno misto zakazovalo neco, co aplikace jinde bezne dela - a zaplatilo za to celym uzitkem te funkce. Radek volani se ted rozklikne na dve veci: - rozpad po parametrech: jmeno, cesta, co se cekalo a co na te ceste opravdu bylo, vcetne nahledu hodnoty. Sklada ho readPayload, protoze jen tam je videt kontrakt i telo naraz - pozdeji uz se kontrakt muze zmenit - cele prijate telo, vcetne klicu, ktere odesilatel posila navic a kontrakt o nich nevi. Delsi nez 8 kB se usekne Telo se do zaznamu zmrazi na retezec, aby se pozdeji nezmenilo pod rukama, a serializace je v try - cyklicka struktura nesmi shodit prijem webhooku. Overeno na bezici instanci s vlastnim DATA_DIR, telem s data jako textem misto objektu a bez povinneho status: ok callSid | cekame string, povinny | prislo text = CA-B CHYBA status | cekame string, povinny | prislo neprislo CHYBA data | cekame object | prislo text = tohle je text misto objektu Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e8bb4d6e54 |
Kontrakt webhooku: objekt jde vybrat a odmitnuti je videt
Parametr spoustece `data` byl deklarovany jako type string a povinny, zatimco odesilatel ho posila jako objekt a v prvni zprave hovoru ho jeste nema. Kazde volani proto skoncilo na 400 a automatizace hodinu nedelala nic. Za tim byly tri veci, kazda sama o sobe malicherna: - rucne pridany parametr byl vychozi povinny, zatimco parametr odvozeny z ukazkoveho tela nepovinny. Dve ruzna vychozi nastaveni pro tutez vec v jednom formulari. Nove je nepovinny i rucne pridany: povinny znamena "odmitni volani" a do toho nema nikdo spadnout omylem - objekt a seznam neslo vybrat. declarableFieldTypes nabizel jen string, number, boolean a date, a TriggerConfig.tsx mel jeste treti kopii toho seznamu. Deklarovat data jako objekt tedy neslo, i kdyz matchesType objekt umi a operatorsByType pro nej ma operatory - odmitnuti nebylo nikde videt. Skoncilo jako console.warn v logu kontejneru: zadna udalost, zadny beh, nic na detailu automatizace Ten treti bod je ten podstatny. Chybu v kontraktu udela ten, kdo ho psal, ale 400 dostane odesilatel - a ten s tim nic nenadela, casto je to cizi sluzba, ktera volani neopakuje. Majitel automatizace se nedozvi nic a v portalu vypada vsechno v poradku. Detail automatizace proto ukazuje poslednich deset volani: cas, jestli proslo nebo ne, a u odmitnutych duvod. Telo se schvalne neuklada, duvod uz rika, co je spatne, a drzet payloady by znamenalo mit v pameti kopie zakaznickych dat. Seznam je v pameti, restart ho zahodi. Incident se z toho nezaklada a upozorneni se neposila: staci radek, implementator se ozve sam. Vzorova automatizace ma data opravene na object a nepovinne. Do navrhu 25 jsou zapsana rozhodnuti z diskuze: "moje tickety" jsou tickety prirazene mne, v helpdesku ty, ktere jsem zalozil ja, a helpdeskove pozadavky vidi lide podle teze hierarchie jako tickety. Sekce 6 popisuje tuhle zmenu. Overeno na bezici instanci s vlastnim DATA_DIR: telo s data jako objektem projde, prvni zprava hovoru s data null projde, telo bez callSid se dal odmita, a vsechna tri jsou videt v seznamu poslednich volani. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
72da07debe |
Navrh pristupneho portalu a srovnani vzorove automatizace s instanci
Dve veci, obe bez zmeny chovani aplikace.
Navrh (documentation/25-navrh-pristupny-portal.md):
Vzniklo z otazky, jak portal priblizit cloveku, ktery ho nikdy nevidel.
Odpoved se rozpadla na pet veci, ktere spolu souvisi vic, nez to vypada:
- prehled ukazuje jen cisla, zadna slovesa. Vsech sest widgetu ve vychozi sade
jsou statistiky a seznamy. Navrh pridava "Moje tickety", "Fronta bez
resitele", "Co potrebujete udelat" a "Zaciname". Prvni dva jsou skoro zadarmo,
builtinSources uz ten mechanismus maji
- formulare nemaji spolecnou vrstvu. inputClass je nadefinovany na 13 mistech
a rozesel se do peti ruznych vzhledu, Field je napsany trikrat. V ui/ neni
zadny formularovy prvek. Blokuje to widget akci, protoze NewTicketDialog je
ten modal a formular z nej vytahnout nejde
- hledani neumi to jedine, k cemu je. Klientsky filtr nehleda v obsahu, ve
vlastnich polich ani v externim ID, takze hovor podle callSid se dohledat
neda. Navrh je modal s kriterii, protoze ticket nema pevnou sadu poli
- viditelnost ticketu se neda omezit. Pohled tenant dostane kazdy, kdo do firmy
patri, mine je dobrovolny filtr a ne strop. Navrh vede viditelnost pres
clenstvi (priznak na firme, priznak u kazde skupiny), ne pres role - role jsou
na celou firmu a neumi rict "v jedne sekci vidim vse, v druhe svoje"
- uloziste neprezije nasazeni, coz podpira bod o hledani
Dve veci, ktere stoji za zapamatovani, i kdyby se navrh nikdy nedodelal:
strop viditelnosti nepatri do hledani, ale do cteni ticketu (cesty k ticketum
jsou tri a dve z nich filtruji tickets primo), a pohled a strop nejsou totez.
Ctyri otevrene otazky jsou v zaveru navrhu.
Vzorova automatizace (src/data/automationStore.ts):
seedRealAutomations drzelo starsi podobu stromu nez ta, ktera na instanci
opravdu bezi. Protoze data neprezivaji redeploy, je tenhle seed jedine misto,
kde nastaveni prezije nasazeni - kdyz se rozejde, znamena to po kazdem nasazeni
stavet strom rucne znovu.
Opsano z bezici instance: spoustec ma sest parametru misto tri (pribylo result,
rating a data), krok upsert pise do obsahu {{voicebotId}}, {{data}} a stav bere
z {{result}}, a za nim je podminka nad vysledkem - cokoliv krome "Chybějící
informace" ticket zavre, jinak jde na servicedesk s vysokou prioritou.
Overeno lokalnim startem s vlastnim DATA_DIR: automatizace se nasype, ma ctyri
kroky stejne jako instance, je zapnuta a nema zadny nedodelek.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
1134852bff |
Zalozit nebo doplnit ticket: doplneni konecne doplnuje
Krok mel v poli Obsah nastaveno {{rating}}. Data v behu prokazatelne byla,
v udalostech ticketu je hodnoceni videt cele, ale ticket zustal s prazdnym
obsahem.
intakeEvent deli praci na zalozeni a navazani na existujici ticket a vsechno
z `create` platilo jen pro tu prvni vetev. U existujiciho ticketu se doplnovaly
pouze vlastni pole a stitky, zbytek se tise zahodil. U hovoru to znamena, ze
obsah nedorazi nikdy: prvni zprava jen oznami, ze hovor zacal (in-progress,
data null), a prave ta ticket zaklada. Hodnoceni prijde az posledni zpravou,
kdy uz ticket existuje. Stav byl jedina vyjimka, protoze ho krok nastavuje
zvlast pres updateTicketStatus - proto fungoval a zbytek ne.
Jedno pravidlo misto dvou seznamu poli:
- neprazdna hodnota prepise, prazdna nemaze. IntakeInput ma na to `apply`,
v `create` zustala jen zaloha predmetu a vychozi stav
- prazdna hodnota nemaze schvalne. Prave to byla puvodni obava, kvuli ktere se
zapisovalo jen pri zalozeni: pozdejsi zprava bez jmena zakaznika je bezna
a smazat kvuli ni jmeno by bylo horsi nez ho nedoplnit
- vyjimky zustavaji dve: zaloha predmetu z externiho ID plati jen pri vzniku
a stav chodi pres updateTicketStatus, ktere resi i priznak vyrizeni, cas
vyreseni a pocet znovuotevreni
Data smi chodit po castech:
- vlastni pole typu se scitaji podle klicu. Prvni zprava posle `data`, druha
`data2` a ticket ma obe
- prazdny retezec pole nemaze. Sablona, ktera na nic neukazuje, se dosadi
prazdnem, takze {"vysledek":"{{result}}"} u zpravy bez vysledku posilalo
prazdno a prepsalo tim hodnotu z minule zpravy. Vymazat pole jde poslanim
null, coz uz je zamer
Dalsi dve veci, ktere u toho vyplavaly:
- create.status se do createTicket vubec nepredaval, takze ticket vznikl
s vychozim "Nový" a hned se prepsal. V logu pak stalo "stav Nový ->
completed" u ticketu, ktery v nem nikdy nebyl
- faze byla zrusena uz driv, ale v katalogu po ni zbyval krok "Posunout do
dalsi faze" a pole Faze u zalozeni ticketu. Ticket ani typ ticketu fazi
nemaji, takze krok by selhal na chybejicim skriptu a pole se zahazovalo.
Oboji je pryc
Krok navic v logu rekne, co doplnil: "doplnen TK-123, stav completed, obsah".
Driv radek jen oznamil, ze se ticket doplnil, a nebylo poznat cim.
Overeno na bezici instanci s vlastnim DATA_DIR, tremi zpravami o jednom hovoru:
prvni zaklada ticket s prazdnym obsahem, druha doplni obsah i zakaznika, treti
bez dat je nechava byt a meni jen stav. Scenar s `data` a pak `data2` ma na konci
obe hodnoty.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
35d1e43307 |
Pad vykreslovani uz nesmi shodit stranku a sam zaklada incident
Ukazka tela webhooku shazovala builder pri psani cesty parametru. Chyba sama byla na jednom radku, ale to podstatne je, ze jedna vyjimka pri vykreslovani odstranila celou stranku. Uzivateli zustala bila plocha a rozdelana prace byla pryc. Aplikace na to nemela zadnou pojistku, takze totez mohlo prijit odkudkoli. Pojistka: - nova web/src/components/ErrorBoundary.tsx obaluje obsah portalu. Mistni chyba ted shodi svoji cast obrazovky, ne aplikaci: navigace, prepinac firmy i odhlaseni zustanou funkcni. Odchod jinam pojistku srovna zpatky - je to jedina trida v celem klientovi, React to jinak zachytit neumi Incident: - POST /api/dashboard/client-crash zaklada incident z padu. Bez toho je jedina stopa v konzoli prohlizece uzivatele, kam se nikdo nedostane - title a impact cte zakaznik, detail cte spravce platformy: hlaska, misto v kodu, strom komponent, adresa stranky, ucet, prohlizec a verze buildu - verze buildu je tam schvalne. U tohohle padu se ukazalo, ze bez ni se neda poznat, jestli uzivatel vidi chybu, ktera uz je opravena, nebo novou - tentyz pad na tomtez miste zalozi incident nejvys jednou za deset minut. Pad pri vykreslovani se opakuje pri kazdem prekresleni Sama ukazka: - parametr, kterym vede cesta jineho parametru, uz nedostane zastupnou hodnotu podle typu. Kdyz je jeden parametr `data` a druhy `data.result`, `data` musi byt objekt a zastupna hodnota by ho prepsala - cela ukazka je v try. Je to napoveda a nesmi shodit ani ten kus obrazovky Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4d156d2837 |
MCP EasyWeb podle skutecne specifikace: auth v2 s klicem zarizeni
Predchozi verze posilala na /login jen jmeno, heslo a nazev zarizeni. Server na to odpovidal 400 Bad Request na cokoliv, i na spravne udaje, protoze cekal neco uplne jineho. EasyWeb ma auth v2: token se nevydava proti uctu, ale proti zarizeni, a to je par klicu ECDSA P-256. Jmeno a heslo se pouziji jedinkrat, pri registraci klice, a soucasti registrace je podpis, kterym zarizeni dokazuje, ze privatni klic k poslanemu verejnemu opravdu ma. Od te chvile se podepisuje kazde volani, ktere s tokeny hybe. Overeno proti bezicimu serveru: se spravnym telem uz /login nevraci 400, ale 401 s neplatnymi udaji. Ucty z jejich testovaciho settings.json na verejnych instancich neplati, takze dal se bez skutecnych udaju nedostanu. Prihlaseni: - src/mcp/easyweb/crypto.ts - klice, podpisy, otisky. Podpis musi byt P1363, tedy hole r||s, 64 bajtu. Node podepisuje ve vychozim nastaveni do DER a ten by protistrana neuznala - src/mcp/easyweb/device.ts - klic se vyrobi jednou a prezije restart, uklada se mezi udaje konektoru, ktere uz jsou zasifrovane. Novy priznak `managed` na poli sluzby znamena, ze ho vyplnuje portal a ve formulari se nezobrazuje - src/mcp/easyweb/session.ts - tri tokeny, retez s ustupy (platny pristupovy, obnova obnovovacim, obnova zarizenim, cele prihlaseni), jedno prihlaseni naraz na konektor, tokeny jen v pameti Ta posledni pravidla nejsou opatrnost navic: tokeny jsou jednorazove, druhe pouziti server odmita kodem 409 a umi zarizeni zablokovat. Transport: - server si sam vybira, jestli odpovi JSON telem nebo SSE streamem, a streamem odpovida i na obycejna volani. Klient nabizi obojí a cte stream po kouscich - u dlouhych uloh ho server sam nezavira - handshake plati na token, ne na volani - odmitnute sezeni prijde jako chyba -32008 uvnitr uspesne odpovedi - seznamy se skladaji pres vsechny stranky, bez toho je videt jen prvni - odpoved se rozbaluje rekurzivne (structuredContent, contents, content, JSON zapsany jako text) Dlouho bezici nastroje se spousti jako uloha a ceka se na ni dotazovanim. Limit kroku se pri tom posouva z patnacti sekund na deset minut. Nedodelane: trvaly kanal notifikaci (GET SSE), nahravani souboru po castech a hlidani zmen kontraktu podle verze serveru. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
81e4348ad8 |
Dve MCP sluzby: obecna podle specifikace a MCP EasyWeb, strankovani nastroju
MCP je standard, ale prihlaseni k nemu ne. Oficialni specifikace stoji na OAuth 2.1 a objevovani autorizacniho serveru pres .well-known. EasyWeb (Centaur) ma prihlaseni vlastni: POST /login s HTTP Basic vrati trojici tokenu a obnovuje se vlastnimi endpointy. Zadny OAuth, zadne .well-known, jina verze protokolu, zadne SSE ani hlavicka sezeni. Proto dve sluzby, ne jedna s prepinacem: firma pri zakladani konektoru vyplnuje neco jineho. U obecne ID a tajemstvi aplikace nebo hotovy token, u EasyWebu jmeno, heslo a nazev zarizeni. Slucovat to by znamenalo formular, kde je pulka poli vzdycky k nicemu, a hadani, ktera pulka to prave je. Obecna sluzba zustava plnohodnotna. Vlastni server je duvod pridat sluzbu, ne duvod zavrit dvere ostatnim. Pribylo: - src/mcp/dialect.ts - rozdily obou serveru na jednom miste: prihlaseni, verze protokolu, jestli se prijima SSE a jestli se posila Mcp-Session-Id. Rozesete po klientovi by u kazdeho dalsiho serveru pribyl dalsi if na jinem miste - sluzba MCP EasyWeb: adresa, jmeno, heslo, nazev a otisk zarizeni. Prihlasovaci adresy si portal odvodi sam, otisk doplni z ID konektoru - hotovy token u obecne sluzby. Rada verejnych serveru nic jineho nenabizi - objevovani pres WWW-Authenticate, coz specifikace ma jako povinnou cestu. Pouziva se az kdyz obvykla mista selzou, stoji to volani navic - zivotnost z tela tokenu: kdyz server expires_in ani datum neposle, cte se exp z JWT. Presne pripad EasyWebu - strankovani nastroju: nastroj s parametrem cursor dostane v builderu prepinac Nacist vsechny stranky. Kurzor je hodnota z odpovedi, takze v dobe stavby stromu ho nikdo nezna a nejde ho vyplnit dopredu. Krok pak vraci navic items, pages, pageCount a truncated. Strop je 20 stranek Opraveno: prihlaseni driv zkousela password grant a HTTP Basic proti hlavnimu endpointu. Prvni OAuth 2.1 zrusil, druhe neni nikde ve specifikaci a u EasyWebu by stejne neproslo - ten chce Basic na /login, ne na /mcp. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
92fecca70c |
MCP: prihlaseni jmenem a heslem, tokeny si obstarava portal
Predchozi verze chtela po uzivateli token. Spatne zadani: zakaznik dostane ke svemu serveru adresu, jmeno a heslo. Token nedostane a nema jak ho ziskat - vyda ho az autorizacni server a ma omezenou zivotnost. Obstarat ho, hlidat platnost a vcas ho obnovit je prace portalu. Novy src/mcp/auth.ts: - kde se prihlasit se zjisti od serveru pres /.well-known/oauth-protected-resource a metadata autorizacniho serveru. Rucni pole na adresu je jen zaloha pro servery, ktere metadata nemaji - cim se zkusi v poradi client_credentials a password. Ktere z toho firma dostala, se z udaju samych poznat neda a nutit ji to vybirat by znamenalo ptat se na neco, co nevi - bez autorizacniho serveru se posle HTTP Basic. Mensi servery zadny OAuth nemaji a jmeno s heslem je u nich presne tohle - zivotnost urcuje server: token se vymeni minutu pred vyprsenim, obnovi se pres refresh_token, kdyz ho server vydal, a kdyz expires_in chybi, pocita se s peti minutami, tedy odhaduje se dolu - odmitnuty token (401 na token, ktery jsme meli za platny) se zahodi a volani se zopakuje jednou. Podruhe uz ne, to uz nejsou platne udaje Token se drzi jen v pameti. Je kratkodoby, po restartu se o novy rekne znovu, a ulozit ho by znamenalo vsechna rizika ulozeni bez jakekoliv vyhody. Kes drzi otisk udaju, takze zmena hesla ulozeny token zneplatni. Udaje konektoru jsou ted adresa, jmeno, heslo a dve nepovinna pole (adresa pro prihlaseni a rozsah opravneni). Ani jedno nemiri do hlavicky - Authorization se pocita az pri volani z toho, co vydal autorizacni server. Zpusob prihlaseni se pise do hlasky u konektoru: uzivatel vyplnil jmeno a heslo a ma vedet, jak s nimi portal nalozil, nez zacne hledat chybu jinde. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
435e254c90 |
MCP konektory: nacte nastroje ze serveru a udela z nich kroky
Firma si zalozi napojeni na svuj MCP server, stiskne Nacist nastroje a jeho
nastroje se objevi v builderu jako kroky automatizace vcetne toho, jake
promenne prijimaji a jake vraceji.
Pribylo:
- sluzba `mcp` - jedina v katalogu bez pevnych operaci, rekne je az server.
Udaje: adresa serveru, token nebo klic v X-API-Key
- POST /connectors/:id/mcp/tools - zepta se serveru na tools/list a ulozi
vysledek. Je to zaroven overeni konektoru, proto u MCP neni tlacitko Overit
- src/mcp/client.ts - handshake, sezeni z hlavicky odpovedi, odpoved jako JSON
i jako SSE stream, strankovani nastroju, nic z toho nevyhazuje vyjimku
- src/mcp/schema.ts - ze schematu vzniknou pole kroku a zpatky se z vyplnenych
retezcu udelaji argumenty ve spravnych typech. Ten druhy smer je ten
podstatny: server ceka {"limit": 10}, ne {"limit": "10"}
- src/data/mcpTools.ts - nastroje v katalogu, kes nad tim, co je u konektoru
- sloupec `mcp` u konektoru (migrace 004). Bez ulozeni by po restartu zmizely
z katalogu kroky, ktere uzivatel uz ma ve stromech
- vnitrni krok runMcpTool - jedna obsluha pro vsechny nastroje vsech serveru
Rozhodnuti:
- nastroj patri firme, ne katalogu. serviceCatalog(tenantId) bez firmy nevrati
zadny, takze zapomenuty argument znamena "nic", ne "vsechno"
- ID operace nese ID konektoru (tool:<konektor>:<nastroj>), protoze firma muze
mit dva servery a na obou nastroj `search`
- krok se neopakuje, MCP nema idempotencni klic
- chyba nemaze nastroje, vypadek serveru nesmi vymazat kroky z automatizaci
- servery se pri startu neobvolavaji, jeden nedostupny by shodil katalog vsem
Dokumentace: prepsany 24-mcp-konektory.md na skutecny stav, novy
00-pro-programatory.md (rozcestnik, model ctyr pojmu, pravidla, ktera plati
vsude, co je krehke), doplnene 01, 12 a 99.
Mimochodem opraveno: setStatus v connectors/postgres.ts melo v RETURNING
doslovny retezec ${COLUMNS} misto dosazeni, a dva odstavce v dokumentu 12 byly
dvakrat.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
3e6b365eec |
Prepsany navrh MCP: patri k modelu, ne do vyberu kroku
Prvni verze byla postavena spatne. Davala MCP nastroje do vyberu kroku, tedy nastroj vybiral clovek a vyplnil mu pevna pole. Tak MCP nedava nic navic proti HTTP konektoru, ktery uz mame - je to protokol pro modely, kde si nastroj vybira model podle toho, co je zrovna potreba. Byla to zamena kategorie. Spravne: MCP konektor neni zdroj kroku, je to schopnost, kterou dostane krok s modelem. V builderu se objevi krok "Nechat model splnit ukol" a v nem se zaskrtne, ktera napojeni smi pouzit. Krok vraci vysledek plus seznam toho, co model opravdu zavolal - bez nej je to cerna skrinka a do provozu to nepatri. Dokument popisuje dve cesty, lisi se tim kudy tece token zakaznika: - predat server modelu (OpenAI ho zavola sam) - malo prace, ale token jde do OpenAI, server musi byt dostupny z jeho site a volani nejdou pres nas log - byt MCP klientem my - vic prace, ale token zustava u nas, plati nase stropy, redakce i seznamy povolenych IP, a funguje to s jakymkoliv modelem Doporucena je druha. Tvar tool objektu overen proti dokumentaci OpenAI (type, server_label, server_url, headers, authorization, allowed_tools, require_approval). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2d38ac1350 |
Navrh MCP konektoru
Neni to naprogramovane, je to navrh: documentation/24-mcp-konektory.md. Podstata problemu: MCP server je katalog operaci, ktery se zepta az za behu, kdezto nas katalog je znamy pri prekladu. Retezec service.actions -> findOperation -> scriptIdFor se dnes cely pta statickeho katalogu, takze by slo pouzit jen nastroj, ktery uz nekdo predem zapsal do kodu - presny opak toho, o co jde. Navrh: jedna sluzba mcp a kazdy server jako konektor pod ni. Nastroje se doplni do katalogu pres withRuntimeOptions, tedy tim samym zpusobem, jakym uz se doplnuji resitele, skupiny a typy ticketu. Schema vstupu se prevede z JSON Schema na OperationField, co se neprevede skonci jako json. Jen Streamable HTTP, ne stdio: stdio by znamenalo pustit zakaznikuv program uvnitr naseho containeru. Adresa projde stejnou kontrolou vnitrni site jako HTTP a SMTP. Idempotence u MCP nefunguje, proto by se krok neopakoval sam. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e1c52e3da3 |
Transformace maji svou kategorii, pribylo XML
Odstranena sluzba AI zpracovani textu. Delala totez co OpenAI, jen jinymi slovy, a dva zpusoby jak udelat jednu vec znamenaji jen otazku, ktery pouzit. Ukazkove stromy a stopy, ktere na ni odkazovaly, miri na openai.chat. - nova kategorie Transformace dat. Driv byly mezi obecnymi sluzbami vedle webhooku a pauzy; ve chvili kdy jich je vic nez tri, hleda je clovek jako skupinu, ne mezi spoustecemi. - skript transform.to-xml: zapise objekt jako XML vcetne escapovani. Cizi systemy si XML porad rikaji casto (ISDOC, EDI, banky, statni sprava) a bez toho se sestavuje retezcem v HTTP kroku, kde se na escapovani zapomene pokazde. Klice, ktere nejsou platny nazev prvku, se prejmenuji a rekne se to nahlas; klic zacinajici cislici dostane podtrzitko misto odriznuti, jinak by 2024 a 2025 byly totez. - navrh cele skupiny transformaci v 13-transformace-dat.md: formaty, pole a hodnoty, seznamy, text a kontrola, vcetne toho co v seznamu zamerne neni. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
aa0a783095 |
AI zpracovani textu funguje: klic, adresa a tri skripty
Sluzba byla v katalogu bez pristupovych udaju a s appId 'ai-text', tedy mirila na aplikaci, ktera neexistuje. Konektor nemel co vyplnit a krok nemel co vykonat. - sluzba stoji na api.openai.com/v1 stejne jako OpenAI, overeni GET /models - model se bere z konektoru (ctx.config.model), ne z kazdeho kroku zvlast - skripty ai-text.classify, ai-text.extract, ai-text.generate Proc to neni jedna sluzba s OpenAI: tam se posila volny dotaz a vraci volny text. Tady je uloha pevna a vystup taky - category a confidence, objekt s popsanymi poli, text s poctem slov. Na to jde ve strome navazat podminkou, na volny text ne. Zjisteno u toho a zapsane do 21-realne-sluzby.md: proxy vraci na neznamou aplikaci 200 s prazdnym telem, takze overeni konektoru u sluzby bez verifyPath projde i tam, kde nic nebezi. Zatim neopraveno. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d71c111cf3 |
Jazyky, skutecny snimek v heru a opravene titulky
Prepinani jazyku (web/src/i18n/). Vybrany jazyk je jedna hodnota pro celou aplikaci drzena mimo React - tentyz vzorec jako u vybrane firmy, a ze stejneho duvodu: musi byt dostupny i mimo komponenty a prepnuti nesmi byt stav jedne stranky. Volba prezije obnoveni a prepisuje <html lang>. Anglicky slovnik je Partial: co v nem neni, spadne na cestinu. Kdyby musel byt uplny, znamenal by kazdy novy cesky text rozbity build, dokud ho nekdo neprelozi - a vysledkem by bylo, ze se texty nepridavaji. Prelozena je hlavicka, hero vcetne snimku, pas s logy, cisla, zaverecna vyzva a cast paticky; zbytek je mechanicky a popsany v 23-jazyky.md. Snimek v heru vypada jako portal doopravdy. Byl svetly a ukazoval obrazovku, ktera v produktu neexistuje. Ted ma tmavy podklad, tytez zalozky vcetne prepinace firmy, tytez sloupce tabulky i tytez odznaky stavu jako stranka Tickety. Kdyz se navstevnik prihlasi a uvidi neco jineho, prvni dojem z produktu je, ze web lhal. Jmeno znacky si sklada usePageMeta, ne stranky. Bylo napsane natvrdo ve dvaceti ctyrech titulcich, takze prejmenovani zmenilo brand.ts a titulky ne: hlavicka rikala nove jmeno a zalozka v prohlizeci stare. Opraveno: odkaz "Prohlednout si to" vedl na koren domeny misto /apps/<app-id>/sluzby, protoze to byl <a href> a ne Link z routeru. Byl to jediny takovy odkaz v celem webu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
33b4a3fdd6 |
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 - 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> |
||
|
|
73e0ecca4f |
Rebrand na WorkNuke: zelena znacka a prestavene hero
Prevzeti vizualniho smeru z predlohy. Web i portal stoji na tychz tokenech, takze zmena hodnot v index.css se projevila naraz na obojim - portal je zeleny, aniz by se sahlo na jedinou jeho komponentu. Znacka a tokeny: - akcent z cyanove na zelenou #B7FF3C, podklad z modrocerne na zelenocernou #0F1411. Seda dostala zelenou prichut, aby vedle podkladu nepusobila spinave. - druhy akcent a text-gradient se nemazaly, jen presmerovaly do zelene rady. Pouziva je 341 mist a prepsat je vsechna kvuli barve by byla zbytecne velka zmena; fialova z nich zmizela tak jako tak. - stav "v poradku" je nove 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 je plocha bomba misto pismene v gradientnim ctverci. LogoMark je samostatny export, aby sel pouzit i uvnitr snimku v heru. Novy favicon. - primarni tlacitko je plna zelena misto prechodu ze dvou barev. Hero a navigace: - 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. 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. - presah snimku se pocita ze sirky kontejneru. Pevna hodnota ve vw stacila na notebooku, ale nad 1600 px se snimek vesel do postranni mezery a prestal byt oriznuty. Zamerne nezmeneno: 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. Popis, jak znacka funguje v kodu, je v documentation/22-znacka-a-design.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2e863c1f8b |
Prepinac firmy do sidebaru a prepnuti prekresli obsah cely
Dodelavka. Tvrzeni "prepinac plati pro cely system" nebylo pravdive: useApiQuery se na zmenu firmy zeptat umel, ale komponenty volajici apiFetch primo - EntityAdmin, a tim cele Nastaveni a zalozky Resitele a Skupiny v Lidech - o zmene nevedely a zustala na nich data predchozi firmy. Presne ta vada, kvuli ktere se to delalo, jen o patro vedle. - obsah dashboardu ma key podle vybrane firmy, takze prepnuti prestavi cely strom komponent. Zadny dotaz nemuze zustat stary, protoze zadna stara komponenta nezustane. Doplnovat zavislost do kazde komponenty zvlast je totez zapomenuti, jen o patro niz. - prepinac je v sidebaru nad navigaci, ne v hlavicce, a vypada jako ovladaci prvek: ram, ikona firmy, sipka. Predchozi verze mela pruhledny ram a splyvala s popiskem. - v hlavicce zustal jen nazev aktivni firmy. Driv tam stalo natvrdo tenants[0], takze po prepnuti ukazovala porad prvni firmu ze seznamu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2e0b9456a9 |
Prava plati za firmu, ne za cloveka
permissionsOf(user) scitalo role pres vsechna clenstvi, takze kdo byl spravce v jedne firme, jednal jako spravce ve vsech, kam patril. Uzivatel ve dvou firmach je vzacny, dusledek ne. - firma je povinny argument u permissionsOf, hasPermission i hasAnyPermission. Zamerne povinny: kdyby byl nepovinny, prvni volani bez nej by tise vratilo vsechna prava. Prekladac tim rovnou nasel vsech ctrnact mist, ktera se ptaji. - kazde misto se pta za tu spravnou firmu: akce nad ticketem za firmu toho ticketu, uprava zaznamu za firmu toho zaznamu, zalozky a helpdesk za firmu, kterou ma clovek prepnutou - 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 - readScope cte z jedne firmy, te prepnute, ne ze vsech. V nastaveni se driv michaly typy ticketu a role napric firmami bez ohledu na vyber. Opraveno u toho: systemove role se pri startu srovnaji s kodem (syncSystemRoles). Vychozi sada se pouzije jen do prazdneho uloziste, takze nove pravo v katalogu se k uz bezici instalaci nikdy nedostalo - ticket.create ani obe prava helpdesku by spravci firmy nenabehla a zalozka Helpdesk by se neobjevila. Meni se jen prava, jen u nasich roli, nazvu se to netyka. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2329ccbdc1 |
Prepinac firmy plati pro cely portal, ne jen pro prehled
Prepinac byl stav uvnitr stranky Prehled, takze prepnuti na LogiTrans zmenilo prehled a nic jineho - ostatni stranky volaly API bez tenantId a server sahnul po vychozi firme. V Lidech tak zustali lide Automie. - vybrana firma je jedna hodnota pro cely dashboard (web/src/lib/tenant.ts) a prezije obnoveni stranky - tenantId doplnuje apiFetch, ne jednotlive stranky. Kdyz si to mela pridavat kazda stranka sama, vetsina na to zapomnela - a prave to byla ta chyba. Nedoplnuje se, kdyz cesta uz firmu nese, u scope=all a mimo /api/dashboard. - useApiQuery se pri prepnuti zepta znovu, jinak by na strance zustala cisla predchozi firmy - prepinac je v hlavicce nad obsahem a nahradil text, ktery ukazoval natvrdo prvni firmu ze seznamu bez ohledu na vyber - ulozena firma, do ktere uzivatel uz nepatri, spadne na vychozi Zjisteno u toho a zapsane do 07-firmy-a-prava.md: prava se scitaji pres vsechny firmy, neprepocitavaji se podle vybrane. Data se tim neprolomi, server je filtruje dal, ale tlacitka ukazuji vic, nez by mela. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a771834e57 |
Realne sluzby, OpenAI, odesilani e-mailu a helpdesk
Katalog srovnany s tim, co opravdu bezi na services.csbot.cz/apps: trinact sluzeb dostalo pristupove udaje a levne cteci overeni, opravena appId, ktera nikam nevedla (ppl, microsoft365, transcription), a GA4, Search Console, Google Ads i Sklik ted stoji na aplikaci analytics, kazda s vlastnimi udaji. Nove sluzby SAP Business One, Google Workspace a Meta Ads. K tomu 23 skriptu, ktere s nimi opravdu neco delaji. OpenAI jako prvni sluzba, ktera nebezi u nas: Service.baseUrl s absolutni adresou, prepis pres <SLUZBA>_BASE_URL nebo adresu u konektoru, predpona hlavicky u pole udaju (uzivatel vlepi holy klic, Bearer dopise runtime). Dotaz na model, nahrani souboru, otazka nad souborem, prepis zvuku. Skript umi odeslat soubor pres ctx.http.postForm (multipart, obsah Base64). Sluzba E-mail pres SMTP. Neni to skript, ale vnitrni krok - SMTP neni HTTP. Konektor nese schranku firmy, krok ma HTML telo, ve kterem se dosazene hodnoty escapuji (znacky autora sablony jsou zamer, ostre zavorky od zakaznika ne). Overeni konektoru se prihlasi na server a nic neodesle. Helpdesk: Ticket.helpdeskSourceId drzi firmu, ktera pozadavek poslala, vlastnikem zustava ta, ktera ho resi - jinak by ho resitel nemel ve sve fronte. Komu pozadavek pripadne, urcuje Tenant.helpdeskProviderId. Zadavatel vidi jen svoje pozadavky a smi k nim pripsat komentar. Opravy v portalu: - hlasky o ulozisti a odchozi IP vidi jen spravce platformy - typ ticketu se v automatizaci vybira ze seznamu firmy, nebo dosadi z dat - stav ticketu je otevreny naseptavac, ne ciselnik - ticket jde zalozit rucne, zakaznik u nej neni povinny - kanal se prejmenoval a parametry u webhooku jsou oznacene jako nepovinne - srovnane markdown tabulky v cele dokumentaci Co z teto davky jeste neni: prepinac firmy je porad jen stav uvnitr stranky Prehled, takze se prepnuti neprojevi v Lidech ani jinde. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6a2e0dc424 |
Zmerit i to, jak nas vidi vlastni proxy
Echo sluzba na internetu odpovi verejnou adresu. Jenze volani na vlastni domenu se otaci zpatky na tentyz stroj a reverse proxy pak vidi neco jineho, typicky adresu docker bridge. A prave tu porovnava seznam povolenych IP u sluzeb za toutez proxy, takze verejna adresa muze byt povolena a volani z containeru presto skonci na 403. Zmeri se to tak, ze portal zavola svoji vlastni verejnou adresu (PUBLIC_ORIGIN + ROOT_PATH + /whoami) a precte si, jak k nemu volani doslo. Kruh sam pres sebe, ale nic jineho tuhle adresu nezjisti: mezi container a server se tim dostane ta sama proxy, kterou prochazi volani na sousedni sluzby. Novy /api/whoami je zamerne bez prihlaseni. Vraci volajicimu jeho vlastni adresu, tedy nic, co by uz nevedel, stejne jako kterakoliv echo sluzba. Obe mereni bezi naraz a jsou videt na strance Konektory vedle sebe. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4545de8076 |
Odchozi IP adresa portalu
Kdyz cizi sluzba odmitne pristup, prvni otazka je, z jake adresy se vlastne vola. Z containeru to videt neni, vidi to az protistrana, takze se zepta echo sluzby podle EGRESS_IP_URL a vysledek se drzi v pameti po EGRESS_IP_TTL_MS. Prazdna EGRESS_IP_URL funkci vypne, prepsat ji jde na vlastni echo pod svou domenou. Adresa je natvrdo na strance Konektory a u kazdeho odmitnuteho overeni v logu. Pripojuje se jen u 401 a 403 - jinde nema co rict a nestoji za volani ven. Neni to tajemstvi: kazda volana sluzba tuhle adresu stejne vidi. Endpoint egress-ip je registrovany pred GET /:id, jinak by ho router vzal jako id konektoru. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ab88979627 |
Dialog portalem do body, hlavicky odpovedi u chyby
Dialog se vykresloval uvnitr karty konektoru misto pres obrazovku. Samo position: fixed nestaci: rodic s backdrop-filter (nase .glass, tedy skoro kazdy panel a karta) je pro fixed potomka containing block. Modal proto jde portalem do document.body. Tykalo se to vsech dialogu, videt to bylo az u Logu, ktere jsou v male karte. K chybe se zapisuji vybrane hlavicky odpovedi: server, via, content-type, www-authenticate, retry-after, x-request-id, date. Rikaji, kdo odpoved vydal. Server: Kestrel je sama aplikace, Via: 1.1 Caddy proxy pred ni. U 403 od proxy byva telo prazdne a bez hlavicek by nezbylo vubec nic. Allowlist, ne vsechno: Set-Cookie a podobne do zaznamu nepatri. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5a124a53d8 |
Chybova hlaseni konektoru rikaji, co se stalo, a kam to slo
Duvod od sluzby jde primo do hlasky: z tela odpovedi se vytahne detail, error_description, message, title, error i seznam missingHeaders. Retezec, ktery vypada jako JSON, se rozbaluje dal - sluzba iDoklad presne takhle predava telo od iDokladu samotneho. Kdyz sluzba nenapsala nic, rekne se to. 401 a 403 uz nejsou jedna hlaska. 401 = udaje sluzba dostala a neuznala je. 403 = tvar udaju je v poradku, zakazuje se samo volani. V kazde hlasce je cela adresa vcetne serveru (ScriptRequestInfo.url), bez query - v query muze byt tajemstvi. 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 a v odpovedi na test, i kdyz overeni projde. Tlacitko Logy na karte konektoru a historie poslednich peti overeni. Odpoved sluzby dosud 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, endpoint GET /connectors/:id/checks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6297bbf480 |
Vystup z vetve plati i za podminkou, samostatny krok Zalozit kontakt
Bezny postup nesel poskladat z kroku: najdi podle ICO, kdyz neni zkus mail, kdyz porad neni zaloz - ID dava jednou jedna vetev a jednou druha, ale rozsah vystupy z vetvi za podminku nepoustel. Slucovat kvuli tomu hledani a zakladani do jednoho kroku bylo obejiti nasi chyby, ne reseni. - Vystup z vetve je za podminkou k dispozici, jen jako nepovinny. Ze muze chybet, se neztratilo: builder to u pole ukaze a pri behu se dosadi prazdno. - Vystup se stejnym jmenem uz z nabidky nemaze ten starsi. Po druhem hledani kontaktu zmizelo ID z prvniho, tedy to, co je v tu chvili potreba. Odkaz se jmenem kroku je jednoznacny. - Duplicitni jmena u vystupu kroku uz nejsou nedodelek. Konflikt zustava mezi parametry spoustece, kde zadny prefix neni. - Novy krok Zalozit kontakt. Nic nedohledava, hledani je vlastni krok. Najit nebo zalozit zustava pro toho, komu staci jistota jednim krokem. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e4779caf82 |
Vlastni skripty firmy: prevod dat v JS, v logu vstup i vystup
Klikaci pravidla jsou u peti poli rychlejsi, ale u modelu objednavky je jich dvacet a v tom se necte. Vedle nich proto skript firmy: prevod z A do B napsany v JS, jeden na zakaznika. - Skripty se ukladaji do uloziste, ne na disk. Disk je uvnitr kontejneru a redeploy ho vymaze. - Krok Transformace dat - Vlastni skript. Vysledek jde dal jako krok.result. - V logu ticketu je u kroku vstup i vystup. Prave to byl duvod, proc skript nad pravidly vyhral. - Zkouska bez ulozeni: v portalu se vlepi skutecne telo a hned je videt, co z toho leze. - Skripty jsou v zalozce Akce, vedle definic akci. Obojí je popis toho, co aplikace ve firme umi, a spravuje to tentyz clovek. Skript je ciste prevod hodnot: dostane input, vrati objekt. Nema require, import, process, fetch ani console, bezi nejvys 2 s a vysledek se vejde do 256 kB. node:vm neni bezpecnostni hranice proti nekomu, kdo se chce dostat ven - je to izolace proti nehode a proti zacykleni. Pri zkousce se ukazalo, ze casovy limit nepokryval samotny beh: runInContext jen vyrobil funkci a zavolat ji zvenku znamenalo, ze while (true) uvnitr zablokovalo proces navzdy. Kod se ted vola uvnitr runInContext. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9b18531d3e |
Prace nad celym modelem: cesty, ukazka tela a smycka nad seznamem
Odesilatel posila cely model. Objednavka ze Shoptetu ma zanoreni, ceny
v podobjektech a seznam polozek - a dosud sel napojit jen plochy seznam
skalarnich poli, takze items[] neslo pouzit vubec.
- Odkaz v sablone muze byt cesta: {{data.order.billingAddress.city}},
{{data.order.items[0].name}}, {{st_faktura.invoiceId}}. Overuje se prvni
cast odkazu, takze ploche odkazy funguji dal presne jako driv.
- Cele telo je v kontextu i v puvodnim tvaru. Deklarovane parametry maji
pri shode jmen prednost.
- Spoustec si pamatuje ukazku skutecneho tela. Server z ni odvodi model,
tedy seznam cest i s typy, a ten se v krocich klika misto opisovani.
Tlacitko doplni z hodnot v ukazce parametry pro podminky.
- Novy krok Pro kazdou polozku: projde seznam a za kazdou polozku vykona
vnoreny podstrom. Uvnitr je item a index, po skonceni krok.results se
seznamem vysledku. Kazdy vysledek nese i puvodni polozku - radek
objednavky potrebuje jak ID z CRM, tak mnozstvi z puvodnich dat.
Strop 200 polozek, mimo seznam krok selze s tim, co tam misto nej je.
- Prevod Za kazdou polozku seznamu v klikacim editoru mapovani. Engine ho
umel, sel ale napsat jen rucnim JSONem.
- Typograficke uvozovky a sipka z kodu pryc.
Overeno nad skutecnym modelem objednavky: 23 cest vcetne
data.order.items[].unitPrice.withoutVat, sablony s cestou i s indexem,
smycka nad dvema polozkami s posbiranymi vysledky.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
fc0c2ff6c7 |
Opakovani do logu nepise a ticket se rodi s poslanym stavem
Overeno proti serveru: tri shodne POSTy udelaly jednu udalost s pocitadlem 3, ale krok se do logu porad zapsal trikrat. Zadani bylo, ze opakovani ma byt informace, ne dalsi radek. - Krok muze rict quiet a jeho radek se do logu ticketu nezapise. - Ticket vznika uz s poslanym stavem. Predtim se zalozil s vychozim "Novy" a hned se prepsal, takze v logu stalo "stav Novy -> completed" u ticketu, ktery v tom stavu nikdy nebyl. Odtud i to "Novy" ve widgetu. - Zaznam zmen: celkovy pocet behu se pocita od zavedeni historie po dnech, puvodni citac se den ode dne nedelil a rozpocitat ho zpetne neni z ceho. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f1e8253169 |
Log rekne co zpusobilo jakou zmenu, prevzeti ticketu a pozvanky
Nalezeno na bezicim serveru: TK-4946 mel 177 udalosti a 620 radku logu, pritom se skoro nic nestalo. Zmereno proti fronte: ve stejnem okne vzniklo presne tolik behu, kolik prislo udalosti (22 a 22), kazdy s jednim pokusem. Fronta nenasobi nic, odesilatel poslal 177 POSTu. Nase vina byla, ze to z historie neslo poznat. - Data udalosti se ukladaji. Kdyz krok nema vlastni, ulozi se to, cim beh zacal - u webhooku prijate telo. Prazdna udalost je horsi nez zadna. - Shodna udalost se pocita (repeats, lastAt), nezaklada dalsi radek. Ticket se pritom nemeni, takze duplikat nerozblika dashboard ani nespusti automatizaci na zmenu ticketu. Zahodit ji nejde, jinak by nikdo nezjistil, ze proti nam neco tluce. - Zmeny se radi pod udalost, ktera je zpusobila, a u udalosti stoji jmeno automatizace. Log se cte jako "prislo tohle -> zmenilo to tohle". - Poznamka o stavu jen kdyz se stav zmenil. "z in-progress na in-progress" u kazde zpravy byl zdroj tech 620 radku. - runsToday konecne znamena dnes: behy po dnech, k tomu vcera a celkem. Dosud to byl citac od zalozeni automatizace, jen se jmenoval "dnes". Vedle toho prace, o kterou slo predtim: - Prevzeti ticketu ze skupiny (POST /tickets/:id/claim) a krok Predat skupine s prepinacem automatickeho prideleni nejvolnejsimu. - Pozvanky do firmy: odkaz s nahodnym kodem, heslo si nastavi pozvany. - Resitele, skupiny a pozvanky presunuty z Nastaveni do zalozky Lide, cleny skupiny se vybiraji klikanim. - Ctyri AI znaky, ktere zbyvaly v kodu, pryc. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
fbe5b8ff6f |
Vyrizeno je vyslovny priznak, ne hadani ze stavu
`Ticket.closed` se 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.closedStatuses` zruseno, byla to tatáz obchazka o uroven vys. Kroky `ticket/upsert` a `ticket/set-status` maji vstup Vyrizeny s trema stavy: ano, ne, prazdne. Prazdne znamena nemenit - jinak by kazda zmena textu stavu mimochodem otevrela vyrizeny ticket. `POST /tickets/:id/status` prijima `closed` jako nepovinny bool a podminka ve strome se na nej muze zeptat. Overeno 8 kontrolami: ticket se stavem completed neni automaticky vyrizeny, dokud to nekdo nerekne. Automatizace s podminkou status = completed zabere na ticketu, ktery do toho stavu prejde, ale na uz existujici tickety nesahne - spousti ji udalost, ne stav. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
202d525905 |
Stav ticketu je volny retezec, ciselnik pryc
Ciselnik new/open/waiting/resolved je pryc. Tickety chodi z cizich aplikaci, ktere maji svoje stavy - voicebot posila ringing a completed. Nutit je do nasi ctverice znamenalo, ze u ticketu svitilo "Novy", i kdyz byl podle odesilatele davno hotovy. Misto nej priznak `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 jen matlo. Vyber stavu v detailu nabizi stavy typu, doporucene a ten, ktery ticket ma prave ted, aby hodnota z cizi aplikace ze seznamu nezmizela. Filtr v seznamu nabizi stavy, ktere v datech opravdu jsou. Overeno 11 kontrolami: ticket z voicebota ma stav ringing, pak in-progress a completed, completed se pozna jako hotovo a zmizi z fronty, filtr i widget ukazuji tvoje stavy a rucne jde nastavit i "ceka na zpetne volani". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ef3080d3f6 |
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 trema vecmi: Ukazkova data jen se SEED_DEMO=1. Automatizace, tickety a incidenty se uz po kazdem nasazeni nevraci. Konfigurace 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 ve svem kodu. 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. Dal: - `ticket/upsert` umi vsechna pole ticketu: zakaznik, 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. - Seskupovani widgetu podle faze a dva nove widgety: tickety podle stavu a podle faze za tento mesic, obojí s proklikem na vyfiltrovany seznam. - Faze se ukazuje jako stav. Driv byl videt jen nas ctyrprvkovy ciselnik, coz u ticketu z cizi aplikace nedava smysl. Zivotni cyklus zustava vedle jako drobny text, protoze se z nej pocitaji statistiky. Overeno 18 kontrolami proti bezicimu serveru. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6abac82d5e |
Krok prirazeni resiteli a ukazkova data mimo zivy provoz
Opraveno: `ticket/assign` nemel vykonnou cast, takze krok "Prirad resiteli" vzdycky selhal hlaskou "operace nema vykonnou cast". Doplnen jako vnitrni krok vedle prirazeni nejvolnejsimu ze skupiny a prirazeni podle externiho ID. Ukazkova automatizace "Smerovani ticketu na resitele" je vypnuta. Zapnuta prebirala tickety, ktere uz nekomu patrily podle skutecne automatizace zakaznika, a prepsat rucni nebo cizi rozhodnuti je to nejhorsi, co muze automatizace udelat. Do udaju spoustece zaroven pribylo `assigned` a `knownCustomer`, aby slo napsat podminku "uz je prirazeny, nesahej na to". `ticket/upsert` prijima `status`: kdyz hodnota patri mezi nase ctyri stavy, nastavi stav, jinak se ulozi jako faze. Cizi aplikace posila svoje stavy hovoru a nas zivotni cyklus je pevny, protoze se z nej pocitaji statistiky. Do shrnuti kroku se napise, co se stalo. Overeno pripadem z provozu: 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 vcetne prokliku na vyfiltrovany seznam. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a57eca123e |
Fronta a worker: webhook odpovi hned, praci udelaji workeri
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 pri timeoutu. Fronta ma opakovani s rostouci prodlevou (30 s, 2 min, 10 min, hodina), spravedlive poradi po firmach (jedna firma s tisicem udalosti nezablokuje ostatni), navrat zaseknutych behu po restartu a uklid hotovych. Marna chyba se neopakuje - chybejici skript za minutu existovat nezacne. Tri druhy spoustecu: push (webhook), vnitrni udalost (vznik a zmena ticketu) a pull, tedy pravidelne dotazovani u sluzeb bez webhooku (posta, zpravy). Planovac jen rekne "je cas", samotny dotaz je prvni krok stromu, takze ma zaznam v logu a opakuje se pri chybe jako cokoliv jineho. Kontrakt tela webhooku: kazdy parametr ma cestu (data.order.id, errors.0.message), takze jde napojit i odesilatel s vnorenym modelem. U adresy je metoda, ukazka tela a kopiruje se cela adresa vcetne domeny. Vnitrni kroky, ktere sahaji do naseho uloziste: ticket/upsert (zaloz nebo dopln podle externiho ID), assign-least-busy, assign-by-external, set-type, set-stage, add-tags, set-status, incident/create, flow/pause a flow/log. Faze ticketu 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: voicebot posle voicebotId a ticket skonci u toho, komu patri. Vazba je na jednom miste, ne v kazde automatizaci. Kazda chyba zaklada incident 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 vidi jen spravce platformy. Ochrana proti smycce: automatizace navazana na zmenu ticketu ticket meni, cimz se spousti znovu - pri vyvoji to server polozilo. Resi to oznaceni behu pres AsyncLocalStorage a strop peti behu na jeden ticket za minutu. Upozorneni pri prideleni prace vcetne cisla u zalozky Tickety. Zivy dashboard: dlazdice nad nasimi daty na udalost, data z konektoru podle ttlSec s moznosti vynutit nacteni znovu. Opraveno: path a intervalSec u spoustece se pri ulozeni zahazovaly; nad seznamem neslo pouzit contains, takze na stitky neslo postavit podminku; novejsi vystup kroku ted prekryje starsi misto hlaseni konfliktu. Overeno dvema scenari proti bezicimu serveru, 34 kontrol: firma se skladem, expedici a IT, a hovory z voicebota (callSid do externiho ID, status do faze, prirazeni podle voicebotId, tri zpravy = jeden ticket se tremi udalostmi). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5d186dcd2e |
Runtime vykonava strom, prokliky z widgetu, oprava ukladani rozlozeni
Runtime: `src/runtime/executor.ts` jde krok po kroku, u podminky se vetvi, do poli dosadi parametry, akci pusti pres runScript a vystupy pripise do kontextu pro dalsi krok. Cely prubeh jde do logu ticketu vcetne toho, co sluzba vratila. Pouzivaji ho obe cesty: akce na ticketu i webhook. Opraveno: rozlozeni dashboardu s vlastnim widgetem se NEDALO ULOZIT. `validateLayout` znala jen vestaveny katalog, takze kazdy pokus skoncil hlaskou "widget v katalogu neexistuje" - presne to, co hlasil uzivatel. Katalog je ted jedna funkce a pouziva ji nabidka i kontrola. Zaroven je za konkretni firmu, driv slo polozit dlazdici jedne firmy na dashboard druhe. Prokliky: z widgetu lidi na cloveka, ze seskupeni na vyfiltrovany seznam ticketu. Odkazy sklada server, protoze on jediny zna filtr widgetu. Seznam ticketu cte filtr z adresy a umi filtrovat na typ, tag a skupinu. Tabulky: spolecna `TicketTable` pro seznam i detail osoby. Na mobilu se neposouva do strany, uzka obrazovka dostane karty. Detail osoby ma velkou tabulku se zalozkami "ma u sebe" a "vyresil" a prepinacem pohledu. Odebrano: simulace vcetne tlacitka, dialogu i endpointu. Trojice pohledu nad tickety - vyber firmy je select, "moje" je prepinac, driv to delalo totez dvakrat. Pridan zmereny rozbor kapacity pro 200 firem (19-kapacita-200-firem.md): soucasny stav to nezvladne, protoze data jsou v pameti a vypis je linearni. Zmereno na 5 000 ticketech, vcetne toho, co s tim a kolik serveru to chce. Overeno 7 kontrolami proti bezicimu serveru. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
29de584df8 |
Ticketovaci system: udalosti, externi ID, statistiky a widgety nad konektory
Jakakoliv udalost se muze stat ticketem. Prijem je verejny endpoint na firmu (`POST /webhook/ticket/:token`), takze zalozit ticket jde i bez stavby stromu. Externi ID je unikatni V RAMCI FIRMY: dalsi zprava se stejnym ID se navesi na existujici ticket misto zalozeni druheho, a stejne ID u jine firmy je jiny ticket. Cislo a retezec jsou tentyz klic. Udalosti se drzi cele vcetne prijatych dat a jdou rozbalit v detailu - je to neco jineho nez log. Ticket nove nese firstResponseAt, resolvedAt, resolvedById a reopenCount. Bez nich neslo rict, kdo kolik odbavil ani jak dlouho zakaznik cekal. `getAgentStats` z toho pocita vykon resitelu vcetne medianovych casu a vracenych ticketu. Pocet vyresenych sam o sobe odmenuje toho, kdo tickety zaviral predcasne, proto je vraceni videt vedle nej. Widgety: klient konecne vola /widget-data. Endpoint existoval, ale nikdo ho nepouzival, takze vlastni widget hlasil "nepodarilo se zobrazit". Pribyl zdroj `connector` - co umi zjistit napojena sluzba, jde vytahnout do dlazdice pres tentyz skript, ktery pouziva krok automatizace. Vysledek se cachuje. Akce a widgety uz nejsou v nastaveni, maji vlastni zalozku vedle automatizaci. Telo akce se sklada stromem, ne JSONem v textarei - je to tentyz editor, jen misto karty spoustece je "spousti clovek tlacitkem na ticketu". Nova zalozka Lide se seznamem a detailem osoby. Seznam ticketu i lidi ma dva pohledy, tabulku a dlazdice. Opraveno: createTicket bral typeId, fields, tags i assigneeGroupId, ale nikdy je neukladal. Ticket zalozeny s typem zustaval bez typu a bez vlastnich poli. Dlouhe pomlcky, sipky, vypustky a bullety pryc z celeho projektu. Overeno 21 kontrolami proti bezicimu serveru v rezimu souboru. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6c90372991 | test | ||
|
|
afbe948da3 |
Uloziste pro vsechna data, oprava .gitignore, dodelany navrh rozsireni
.gitignore mel vzorec `data/`, ktery se shodl i se `src/data/`. Sestnact zdrojovych souboru tim tise chybelo v gitu vcetne cele slozky `src/data/store/`. Opraveno na `/data/`, stejne v .dockerignore. Tickety vcetne logu, automatizace, incidenty a rozlozeni dashboardu se po kazde zmene ukladaji. Pomocnik `withMirror` je opak `withCache`: data se meni v pameti a zapisuji cela, misto aby se po zapisu znovu nacitala. Citace ID se pri startu dopocitaji z ulozenych zaznamu, takze novy ticket neprepise stary. Detail ticketu umi typ, tagy, vlastni pole typu a prehozeni na skupinu. Nastaveni ma prepnuti spravce na jiny ucet, vychozi jen pro cteni. Skupiny resitelu chodi spolu s lidmi jednim requestem. Dokumentace: rejstrik znovupouzitelnych funkci (15), navrh monetizace a ceny za krok (16), popis nastaveni a prav (17). Doplneny endpointy do openapi.ts, petice CRUD rout se generuje jednou funkci. Overeno v rezimu souboru: zmeny prezily tvrde ukonceni procesu a po restartu byly zpatky vcetne logu ticketu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |