# 99 - Zaznam zmen Nejnovejsi nahore. ## 2026-08-28 - MCP: prihlaseni jmenem a heslem, tokeny si resi portal Predchozi verze chtela po uzivateli token. To bylo spatne zadani: zakaznik dostane ke svemu serveru **adresu, jmeno a heslo**, token nedostane a nema jak ho ziskat - vyda ho az autorizacni server a ma omezenou zivotnost. Novy `src/mcp/auth.ts` resi cely zivotni cyklus: - **Kde se prihlasit** se zjisti od serveru pres `/.well-known/oauth-protected-resource` a metadata autorizacniho serveru. Rucni pole na adresu je jen zaloha pro servery, ktere metadata nemaji. - **Cim** se zkusi v poradi `client_credentials` a `password`. Ktere z toho firma dostala, se z udaju samych poznat neda a nutit ji to vybirat by znamenalo ptat se na neco, co nevi. - **Bez autorizacniho serveru** se posle HTTP Basic. Mensi servery zadny OAuth nemaji a jmeno s heslem je u nich presne tohle. - **Zivotnost urcuje server.** Token se vymeni minutu pred vyprsenim, obnovi se pres `refresh_token`, kdyz ho server vydal, a kdyz `expires_in` chybi, pocita se s peti minutami - tedy odhaduje se dolu. - **Odmitnuty token** (401 na token, ktery jsme meli za platny) se zahodi a volani se zopakuje jednou. Podruhe uz ne, to uz nejsou platne udaje. Token se drzi **jen v pameti**. Je kratkodoby, po restartu se o novy rekne znovu, a ulozit ho by znamenalo vsechna rizika ulozeni bez jakekoliv vyhody. Kes si drzi otisk udaju, takze zmena hesla ulozeny token zneplatni. Zpusob prihlaseni je videt v hlasce u konektoru: uzivatel vyplnil jmeno a heslo a ma vedet, jak s nimi portal nalozil, nez zacne hledat chybu jinde. ## 2026-08-28 - MCP konektory hotove Firma si zalozi napojeni na svuj MCP server, stiskne **Nacist nastroje** a jeho nastroje se objevi v builderu jako kroky. Popis v [24-mcp-konektory.md](24-mcp-konektory.md). Navrh byl predtim dvakrat prepsany a stoji za to rict proc. Prvni verze davala nastroje do vyberu kroku, ale popisovala MCP jako obycejny katalog vzdalenych procedur - tak nedava nic navic proti HTTP konektoru. Druha verze to prehnala opacnym smerem a chtela nastroje predavat jen modelu. Vysledne zadani je uprostred: **klientem jsme my**, nastroj vybira clovek v builderu, a prinos je v tom, ze napojeni na cizi system vznikne bez radky kodu u nas. ### Pribylo - **Sluzba `mcp`.** Jedina v katalogu, ktera nema zadne pevne operace - rekne je az server. Udaje jsou adresa serveru, token nebo klic v `X-API-Key`. - **`POST /connectors/:id/mcp/tools`.** Zepta se serveru na `tools/list` a ulozi vysledek. Je to zaroven overeni konektoru, proto tlacitko "Overit" u MCP neni. - **Klient protokolu** (`src/mcp/client.ts`): handshake, sezeni z hlavicky odpovedi, odpoved jako JSON i jako SSE stream, strankovani nastroju. - **Prevod schemat** (`src/mcp/schema.ts`). Ze schematu vzniknou pole kroku a zpatky se z vyplnenych retezcu udelaji argumenty ve spravnych typech. Ten druhy smer je ten podstatny: server ceka `{"limit": 10}`, ne `{"limit": "10"}`, a rada serveru na tom spadne az uvnitr nastroje. - **Nastroje u konektoru** (sloupec `mcp`, migrace `004`). Bez ulozeni by po restartu zmizely z katalogu kroky, ktere uzivatel uz ma ve stromech. - **Vnitrni krok `runMcpTool`.** Jedna obsluha pro vsechny nastroje vsech serveru - ktery to je, rika az ID operace `tool::`. ### Rozhodnuti, ktera stoji za zminku - **Nastroj patri firme, ne katalogu.** `serviceCatalog(tenantId)` bez firmy nevrati zadny nastroj. Zapomenuty argument tak znamena "nic", ne "vsechno" - stejne pravidlo jako u filtru na firmu. - **ID operace nese ID konektoru.** Firma muze mit dva servery a na obou nastroj `search`. - **Krok se neopakuje.** MCP nema idempotencni klic, druhy pokus po timeoutu by nastroj provedl podruhe. - **Chyba nemaze nastroje.** Vypadek serveru nesmi vymazat kroky z hotovych automatizaci. Prazdny seznam se naopak ulozi. - **Servery se pri startu neobvolavaji.** Jeden nedostupny by shodil katalog vsem. ### Opraveno mimochodem - `setStatus` v `connectors/postgres.ts` melo v `RETURNING` doslovny retezec `${COLUMNS}` misto dosazeni. V rezimu `file`, ve kterem bezi nasazeni, se to neprojevilo. - Dva odstavce v [12-sluzby-a-konektory.md](12-sluzby-a-konektory.md) byly dvakrat. ## 2026-08-28 - dokument pro programatory Novy [00-pro-programatory.md](00-pro-programatory.md): rozcestnik, model ctyr pojmu (sluzba, skript, konektor, krok), pravidla, ktera plati vsude, tri cesty vzniku operace, tri rezimy uloziste a seznam mist, ktera jsou krehka. Neni to kopie [03-architektura-a-mapa-kodu.md](03-architektura-a-mapa-kodu.md). Ta rika, kde co lezi, tenhle rika proc to tak je. ## 2026-08-28 - transformace maji svou kategorii, pribylo XML ### Odstraneno - **Sluzba AI zpracovani textu.** Delala totez co OpenAI, jen jinymi slovy. Dva zpusoby, jak udelat jednu vec, znamenaji jen otazku, ktery pouzit. Ukazkove stromy a stopy, ktere na ni odkazovaly, ted miri na `openai.chat`. ### Pridano - **Kategorie Transformace dat.** Driv byly mezi obecnymi sluzbami vedle webhooku a pauzy. Ve chvili, kdy jich je vic nez tri, je clovek hleda jako skupinu, ne mezi spoustecemi. - **Skript `transform.to-xml`.** Zapise objekt jako XML vcetne escapovani. Cizi systemy si XML porad rikaji casto (ISDOC, EDI, banky, statni sprava) a bez tohohle se sestavuje retezcem v HTTP kroku, kde se na escapovani zapomene pokazde. Klice, ktere nejsou platny nazev prvku, se prejmenuji a rekne se to nahlas. Klic zacinajici cislici dostane podtrzitko misto odriznuti - `2024` a `2025` by jinak byly totez a v dokumentu by se srazily. - **Navrh cele skupiny transformaci** v [13-transformace-dat.md](13-transformace-dat.md): formaty, pole a hodnoty, seznamy, text a kontrola. Vcetne toho, co v seznamu zamerne neni a proc. ## 2026-08-28 - AI zpracovani textu funguje Sluzba byla v katalogu bez pristupovych udaju a s `appId: 'ai-text'`, tedy mirila na aplikaci, ktera neexistuje. Konektor nemel co vyplnit a krok nemel co vykonat. ### Zmeneno - **Sluzba ma API klic a skutecnou adresu.** Stoji na `api.openai.com/v1` stejne jako OpenAI, overeni je `GET /models`. Model se bere z konektoru (`ctx.config.model`), aby se nevyplnoval u kazdeho kroku znovu. ### Pridano - **Tri skripty**: `ai-text.classify`, `ai-text.extract`, `ai-text.generate`. Proc to neni jedna sluzba s OpenAI: tam se posila volny dotaz a vraci volny text. Tady je uloha pevna a **vystup taky** - `category` a `confidence`, objekt s popsanymi poli, text s poctem slov. Prave na to jde ve strome navazat podminkou, na volny text ne. ### Zjisteno u toho - **Overeni konektoru projde i u sluzby, ktera neexistuje.** Proxy vraci na neznamou aplikaci `200` s prazdnym telem, a sluzba bez `verifyPath` se overuje prave pres `/health`. Konektor tedy rekne "Sluzba odpovida" i tam, kde nic nebezi. Tyka se to E-shopu, WhatsAppu, Facebooku, Instagramu, SMS a Slacku. Zapsane v [21-realne-sluzby.md](21-realne-sluzby.md), zatim neopravene. ## 2026-08-26 - jazyky, skutecny snimek v heru a opravene titulky ### Pridano - **Prepinani jazyku** (`web/src/i18n/`). Vybrany jazyk je jedna hodnota pro celou aplikaci, drzena mimo React - tentyz vzorec jako u vybrane firmy. Volba prezije obnoveni stranky a prepisuje ``. Anglicky slovnik je `Partial`: co v nem neni, spadne na cestinu, takze preklad jde doplnovat po castech misto jednoho velkeho skoku. Podrobnosti v [23-jazyky.md](23-jazyky.md). - **Prepinac jazyka** v hlavicce a v mobilnim menu. ### Zmeneno - **Snimek v heru vypada jako portal doopravdy.** Byl svetly a ukazoval obrazovku, ktera v produktu neexistuje. Ted ma tmavy podklad, tytez zalozky vcetne prepinace firmy, tytez sloupce tabulky i tytez odznaky stavu jako stranka Tickety. Kdyz se navstevnik prihlasi a uvidi neco jineho, prvni dojem z produktu je, ze web lhal. - **Jmeno znacky si sklada `usePageMeta`, ne stranky.** Bylo napsane natvrdo ve dvaceti ctyrech titulcich, takze prejmenovani produktu zmenilo `brand.ts` a titulky ne - hlavicka rikala nove jmeno a zalozka v prohlizeci stare. ### Opraveno - **Odkaz "Prohlednout si to" vedl mimo proxy.** Byl to ``, tedy koren domeny, ne `/apps//sluzby`. Ted je to `Link` z routeru jako vsude jinde. Byl to jediny takovy odkaz v celem webu. ## 2026-08-26 - na aplikaci je videt, ktery build to je Nasazena aplikace o dva commity pozadu funguje a vypada skoro stejne jako nova. Bez oznaceni buildu se to nepozna a hleda se chyba tam, kde zadna neni. ### Pridano - **Oznaceni buildu v pate postranniho menu**, pod tlacitkem Odhlasit se: verze, cas buildu a cislo commitu. Text jde oznacit jednim kliknutim, aby se dal poslat dal. - **Totez jako `` v hlavicce stranky.** V JS balicku uz to je, ale ten se musi stahnout a rozbalit. Meta znacka je videt na jeden `curl` bez prihlaseni - a prave to clovek potrebuje, kdyz zjistuje, co bezi na produkci. - Hodnoty dosazuje Vite pri buildu (`define` a plugin `build-stamp` ve `vite.config.ts`), takze neplati pro repozitar, ale pro ten konkretni balicek. ### Zmeneno - **`.dockerignore` uz nevylucuje `.git/`.** Build z nej cte cislo commitu; bez nej by se poznal jen cas buildu, ne co v nem je. Do vysledneho image se `.git` nedostane, ten se sklada z ciste zakladni image. ## 2026-08-26 - rebrand na WorkNuke Prevzeti vizualniho smeru z predlohy. Popis, jak znacka funguje v kodu, je v [22-znacka-a-design.md](22-znacka-a-design.md). ### Zmeneno - **Barevnost.** Akcent z cyanove na zelenou `#B7FF3C`, podklad z modrocerne na zelenocernou `#0F1411`. Web i portal stoji na tychz tokenech, takze se to projevilo naraz na obojim. - **Druhy akcent uz neni barva.** `accent-*` ukazuje do teze zelene rady jako `brand-*`. Klice zustaly, protoze je pouziva 341 mist; prepsat je vsechny naraz by byla zbytecne velka zmena. Totez u utility `text-gradient`, ktera uz negeneruje prechod, ale plnou zelenou. - **Stav "v poradku" je modrozeleny.** Akcent je zeleny, a dve zelene na jedne obrazovce, kazda o necem jinem, nikdo nerozlisi. - **Pismo.** Archivo na nadpisy, IBM Plex Sans na text, IBM Plex Mono na cisla a ID. Nadpisy maji tesny proklad, ktery Inter neumel bez trikareni. - **Znak.** Plocha bomba misto pismene v gradientnim ctverci. `LogoMark` je samostatny export, aby sel pouzit i uvnitr snimku v heru. - **Tlacitko.** Plna zelena misto prechodu ze dvou barev. Prave tim vystupuje z rady. - **Hero.** Prokladany nadtitulek misto odznaku, dvouslovne radky nadpisu, jedno primarni tlacitko plus textovy odkaz, ctyri slovesa misto tri odrazek a snimek portalu vybihajici z prave hrany. - **Navigace** je pri levem okraji a bez pilulky za aktivni polozkou. Jedina pilulka na liste je primarni akce. - **Nazev, claim a kontakt** v `brand.ts`. ### Opraveno u toho - **Zare v heru je pozadi sekce, ne vrstva pod ni.** Prekryv se zapornym z-indexem se schoval za podklad stranky a nebyl videt vubec. ### Co se zamerne nezmenilo Klice v prohlizeci (`automia.token`, `automia.tenant`), ID firmy `tnt_automia`, ID aplikace `csbot-prototype` a prihlasovaci e-maily v ukazkovych datech. Vypadaji jako jmeno, ale jsou to identifikatory. Duvody jsou v [22-znacka-a-design.md](22-znacka-a-design.md). ## 2026-08-26 - prepinac firmy: do sidebaru a prekresli obsah cely Dodelavka predchoziho bodu. Tvrzeni "prepinac plati pro cely system" nebylo pravdive: `useApiQuery` se na zmenu firmy zeptat umel, ale komponenty, ktere volaji `apiFetch` primo - `EntityAdmin`, a tim cele Nastaveni a zalozky Resitele a Skupiny v Lidech - o zmene nevedely a zustala na nich data predchozi firmy. ### Opraveno - **Obsah dashboardu ma `key` podle vybrane firmy**, takze prepnuti prestavi cely strom komponent. Zadny dotaz nemuze zustat stary, protoze zadna stara komponenta nezustane. Doplnovat zavislost do kazde komponenty zvlast je totez zapomenuti, jen o patro niz. ### Zmeneno - **Prepinac je v sidebaru nad navigaci**, ne v hlavicce. Vypada jako ovladaci prvek: ram, ikona firmy, sipka. Predchozi verze mela pruhledny ram a splyvala s popiskem - prepinac, ktery neni videt, je k nicemu. - **V hlavicce zustal jen nazev aktivni firmy.** Driv tam stalo natvrdo `tenants[0]`, takze po prepnuti ukazovala porad prvni firmu ze seznamu. - Kdo patri do jedne firmy, vidi misto prepinace jeji nazev. Vyber z jedne moznosti neni vyber. ## 2026-08-26 - prava plati za firmu, ne za cloveka `permissionsOf(user)` scitalo role pres vsechna clenstvi. Kdo byl spravce v jedne firme, jednal jako spravce ve vsech, kam patril. Uzivatel ve dvou firmach je vzacny, dusledek ne - to neni edge case, to je diera v pravech. ### Zmeneno - **Firma je povinny argument** u `permissionsOf`, `hasPermission` i `hasAnyPermission`. Zamerne povinny: kdyby byl nepovinny, prvni volani bez nej by tise vratilo vsechna prava. Prekladac tim rovnou nasel vsech ctrnact mist, ktera se ptaji. - **Kazde misto se pta za tu spravnou firmu.** Akce nad ticketem za firmu toho ticketu, uprava zaznamu za firmu toho zaznamu, zalozky a helpdesk za firmu, kterou ma clovek prepnutou. Kdo edituje zaznam jine firmy, musi mit pravo tam, ne tam, kde je zrovna prepnuty. - **Cizi firemni role se ignoruje**, i kdyby na ni clenstvi odkazovalo. Jinak by stacilo pripsat si ji do clenstvi v jine firme a prava by se prenesla. - **Cache prav ma v klici firmu.** Bez toho by prvni dotaz odpovedel i na druhou. - **`readScope` cte z jedne firmy, te prepnute**, ne ze vsech, kam clovek patri. V nastaveni se driv michaly typy ticketu a role napric firmami bez ohledu na vyber - stejna vada jako u prepinace, jen na jinem miste. ### Opraveno - **Systemove role se pri startu srovnaji s kodem** (`syncSystemRoles`). Vychozi sada se pouzije jen do prazdneho uloziste, takze nove pravo v katalogu se k uz bezici instalaci nikdy nedostalo - `ticket.create` a obe prava helpdesku by spravci firmy nikdy nenabehla a zalozka by se neobjevila. Meni se jen prava, jen u nasich roli, nazvu se to netyka. ## 2026-08-26 - prepinac firmy plati pro cely portal Prepinac byl stav uvnitr stranky Prehled. Prepnuti na LogiTrans zmenilo prehled a nic jineho: ostatni stranky volaly API bez `tenantId` a server sahnul po vychozi firme, takze v Lidech zustali lide Automie. To neni nepohodli, to jsou cizi data pod hlavickou jine firmy. ### Zmeneno - **Vybrana firma je jedna hodnota pro cely dashboard** (`web/src/lib/tenant.ts`), ne stav stranky. Prezije obnoveni stranky. - **`tenantId` doplnuje `apiFetch`**, ne jednotlive stranky. Kdyz si to mela pridavat kazda stranka sama, vetsina na to zapomnela - a zapomnetlivost byla prave ta chyba. Nedoplnuje se, kdyz cesta uz firmu nese (proklik z widgetu), u `scope=all` a mimo `/api/dashboard`. - **`useApiQuery` se pri prepnuti zepta znovu.** Bez toho by na strance zustala cisla predchozi firmy, dokud by ji nekdo neobnovil. - **Prepinac je v hlavicce nad obsahem.** Driv byl v Prehledu, ted plati viditelne pro vsechno. Nahradil i text, ktery ukazoval natvrdo prvni firmu ze seznamu bez ohledu na to, co bylo vybrane. - **Ulozena firma, do ktere uzivatel uz nepatri, spadne na vychozi.** Jinak by po odchodu z firmy koncil kazdy dotaz na 403. ### Zjisteno u toho - **Prava se scitaji pres vsechny firmy**, neprepocitavaji se podle vybrane. Kdo je spravce v jedne firme a resitel v druhe, ma po prepnuti porad prava spravce. Data se tim neprolomi, server je filtruje dal podle firmy, ale tlacitka ukazuji vic, nez by mela. Zapsane v [07-firmy-a-prava.md](07-firmy-a-prava.md), sekce Co chybi. ## 2026-08-26 - helpdesk: pozadavek, ktery vidi zadavatel i resitel Zakaznik nemel jak poslat pozadavek. Ticket pritom patri jedne firme, kdezto u helpdesku figuruji dve - ta, ktera se pta, a ta, ktera to resi. ### Pridano - **Pole `Ticket.helpdeskSourceId`** s firmou, ktera pozadavek poslala. Vlastnikem (`tenantId`) zustava ta, ktera ho **resi**. Zamerne tak: kdyby byl vlastnikem zadavatel, mel by resitel pozadavek jen jako cizi ticket a nemel by ho ve sve fronte, ve statistikach ani v prirazovani. Takhle je to na jeho strane obycejny ticket a nemuselo se sahnout na nic, co uz funguje. - **`Tenant.helpdeskProviderId`**, tedy komu firma posila pozadavky. Nastavuje spravce platformy v Nastaveni, Firmy. Kdo koho obsluhuje je obchodni vztah, ne volba klienta - kdyby si dodavatele vybiral uzivatel, poslal by pozadavek nekomu, s kym nema smlouvu. Bez vyplneneho dodavatele se pozadavek nezalozi a rekne se to nahlas. - **Sekce Helpdesk** (`/dashboard/helpdesk`) a router `/api/dashboard/helpdesk`: seznam vlastnich pozadavku, zalozeni, detail s prubehem a komentar. Zamerne to **neni druhy seznam ticketu** - chybi tu filtry, prirazovani i fronta, protoze zadavatele nezajima, kdo to ma u sebe. - **Prava `helpdesk.view` a `helpdesk.create`.** Prideluje je admin te firmy pres role, stejne jako u ostatnich prav. ### Zmeneno - **`listTickets` umi filtrovat podle zdroje** (`helpdeskSourceIds`). Vyplnene **nahrazuje** filtr podle vlastnika, protoze zadavatel vlastnikem neni. Bezny seznam ticketu se nezmenil. - **`getTicket` a `addComment` maji druhou cestu dovnitr.** Firma, ktera pozadavek poslala, ho smi cist a pripsat k nemu komentar, i kdyz ho nevlastni. Komentar je jedina zmena, kterou nad nim smi: stav, resitele a typ urcuje ten, kdo to resi. ## 2026-08-26 - portal prestal ukazovat provozni veci a ticket jde zalozit rucne Sada oprav podle toho, co v portalu drhlo. ### Zmeneno - **Hlasky o ulozisti a o odchozi IP adrese vidi jen spravce platformy.** Kam se uklada a z jake adresy volame ven resime my, ne zakaznik. "Data se ukladaji do souboru na serveru" na nej navic pusobi jako priznani, ze mu tu praci muzeme ztratit. Nemazou se, jen se schovavaji - nam poradi porad. Totez plati pro radek "volano z IP" v historii overeni. - **Typ ticketu se v automatizaci vybira ze seznamu.** Bylo to textove pole, do ktereho mel clovek opsat ID typu odjinud. Novy druh pole `lookup` je **ciselnik a volny text zaroven**: bud se vybere ze seznamu typu te firmy, nebo se hodnota dosadi z dat (`{{data.typ}}`). Typy ticketu jsou vlastnost firmy, takze nabidka chodi za tu, ve ktere clovek je. - **Stav ticketu je otevreny naseptavac, ne ciselnik.** Stav je volny retezec a vzdycky byl - ticket muze prijit z cizi aplikace s jejim vlastnim stavem. Vyber ze seznamu tomu odporoval. Ted je to textove pole s nabidkou toho, co firma uz pouziva (`GET /api/dashboard/tickets/statuses`), a nova hodnota projde stejne dobre. Zapisuje se az pri opusteni pole, ne po kazdem pismenu. - **"Kanal" se prejmenoval na "Odkud pozadavek prisel"** a rika o sobe, ze je nepovinny a slouzi jen k filtrovani a ikone v seznamu. - **Vstupni parametry u webhooku jsou oznacene jako nepovinne.** Webhook prijme cokoliv; rucne vypsany seznam parametru neni podminka, ale pohodli pro podminky - a vyplni se sam z vlepene ukazky tela. Prazdny seznam uz nehlasi, ze "bez nich nelze pridat podminku". ### Pridano - **Zalozeni ticketu rucne** (`POST /api/dashboard/tickets`, pravo `ticket.create`, tlacitko Novy ticket na strance Tickety). Dosud ticket vznikal jen z automatizace nebo z prichozi udalosti, takze pozadavek prijaty telefonem nemel jak do systemu. - **Zakaznik u ticketu je nepovinny.** Rucne zalozeny ticket je casto ukol, ne pozadavek od nekoho zvenku; povinna firma a kontakt by znamenaly, ze si je clovek vymysli. Ve formulari je zakaznik schovany pod odkazem. ### Co z teze davky jeste neni - **Sekce Helpdesk** pro zakaznika vcetne sdileni ticketu mezi admin firmou a tim, kdo ho zalozil. - **Prepinac firmy nad celym dashboardem.** Dnes je to stav uvnitr stranky Prehled, takze se prepnuti neprojevi v Lidech ani jinde - ostatni stranky volaji API bez `tenantId` a server pouzije vychozi firmu. Ma to doplnovat API vrstva na jednom miste, ne kazda stranka zvlast. ## 2026-08-26 - odesilani e-mailu ze schranky firmy Sluzba E-mail byla v katalogu jako popis: bez udaju, bez vykonne casti. Ted se odesila doopravdy, ze schranky, kterou si firma vyplni v konektoru. ### Pridano - **Sluzba E-mail pres SMTP.** V konektoru server, port, sifrovani, uzivatel, heslo, adresa a jmeno odesilatele a adresa pro odpovedi. Zadne udaje v prostredi - kazda firma odesila ze sve schranky. - **Vnitrni krok `email/send`.** SMTP neni HTTP a skript umi jen `ctx.http`, takze operaci vykonava vnitrni krok stejne jako zalozeni ticketu. Pristupove udaje pritom zustavaji v konektoru. - **Druh pole `html`.** Telo zpravy se pise jako HTML a builder ho vykresli jako vysoke pole s neproporcionalnim pismem. Runtime v nem **escapuje dosazene hodnoty**: znacky autora sablony jsou zamer, ostre zavorky v hodnote od zakaznika ne. Bez toho by text ticketu s `` prepsal rozvrzeni zpravy a `