[{"data":1,"prerenderedAt":9539},["ShallowReactive",2],{"blog-page":3,"blogs":17},{"id":4,"title":5,"body":6,"description":7,"extension":8,"meta":9,"navigation":10,"path":11,"seo":12,"stem":15,"__hash__":16},"blogPage\u002Fblog.yml","Blog",null,"Praktyczne notatki z full-stack developmentu — narzędzia, decyzje, lekcje z prawdziwych projektów.","yml",{},true,"\u002Fblog",{"title":13,"description":14},"AWFS – Blog o full-stack development z udziałem AI","Praktyczne notatki z full-stack developmentu — narzędzia, decyzje i lekcje z prawdziwych projektów aplikacji webowych z AI.","blog","GsF98_ZCLrz_ofdotmI-YJMWSVJ-vfquRBHXrMNOMrg",[18,2097,3685,4705,9222],{"id":19,"title":20,"author":21,"body":22,"date":2086,"description":2087,"draft":2088,"extension":2089,"image":2090,"meta":2091,"minRead":6,"navigation":10,"path":2092,"seo":2093,"sitemap":2094,"stem":2095,"__hash__":2096},"blog\u002Fblog\u002Fgrafiki-artykulow-i-obrazy-og.md","Grafiki i obrazy OG — nowe SKILL-e","Artur Wilczek",{"type":23,"value":24,"toc":2068},"minimark",[25,30,57,86,99,103,130,276,297,315,319,322,352,389,410,416,458,477,481,488,495,517,536,542,545,834,841,856,863,868,886,894,908,915,919,933,970,973,1013,1016,1202,1229,1235,1254,1525,1543,1549,1556,1563,1567,1601,1702,1711,1715,1725,1741,1757,1778,1859,1866,1870,1884,1924,1927,1949,1967,2008,2022,2026,2029,2057,2064],[26,27,29],"h2",{"id":28},"wstęp","Wstęp",[31,32,33,34,39,40,44,45,48,49,52,53,56],"p",{},"W ",[35,36,38],"a",{"href":37},"\u002Fblog\u002Fai-driven-development-workflow","poprzednim artykule"," opisałem, jak zbudowałem zespół wyspecjalizowanych ",[41,42,43],"strong",{},"SKILL-i Claude Code"," do prowadzenia projektu — od planowania zadań po dokumentację. Tamten workflow dotyczył kodu. Tym razem chcę pokazać, że ta sama filozofia — ",[41,46,47],{},"wyspecjalizowany agent + skrypt pomocniczy"," — świetnie sprawdza się też tam, gdzie zwykle sięga się po grafika albo po godzinę grzebania w edytorze: przy ",[41,50,51],{},"grafikach nagłówkowych artykułów"," i ",[41,54,55],{},"obrazach Open Graph",".",[31,58,59,60,64,65,64,68,64,71,73,74,77,78,81,82,85],{},"Problem był prozaiczny. Każdy nowy artykuł potrzebuje grafiki w nagłówku. Każda podstrona — ",[61,62,63],"code",{},"\u002F",", ",[61,66,67],{},"\u002Fabout",[61,69,70],{},"\u002Fcontact",[61,72,11],{}," — potrzebuje sensownego obrazu, który pokaże się, gdy ktoś wrzuci link na ",[41,75,76],{},"LinkedIn"," czy do ",[41,79,80],{},"Slack-a",". Robienie tego ręcznie jest trudne i czasochłonne i — co gorsza — ",[41,83,84],{},"niespójne",": za każdym razem trochę inny styl, inny font, inny kadr. A spójność wizualna to jedyne, co odróżnia stronę wyglądającą profesjonalnie od zlepka przypadkowych obrazków.",[31,87,88,89,52,94,56],{},"Postanowiłem to zautomatyzować. Efektem są dwa SKILL-e: ",[41,90,91],{},[61,92,93],{},"generate-article-picture",[41,95,96],{},[61,97,98],{},"generate-page-og-picture",[26,100,102],{"id":101},"czym-jest-open-graph-i-po-co-go-stosować","Czym jest Open Graph i po co go stosować",[31,104,105,113,114,117,118,121,122,125,126,129],{},[41,106,107],{},[35,108,112],{"href":109,"rel":110},"https:\u002F\u002Fogp.me\u002F",[111],"nofollow","Open Graph"," (w skrócie ",[41,115,116],{},"OG",") to protokół zaproponowany pierwotnie przez Facebooka, który mówi serwisom społecznościowym i komunikatorom, ",[41,119,120],{},"jak wyświetlić podgląd linku",". Kiedy wklejasz adres strony na LinkedIn, X, Facebooku, w Slack-u czy iMessage, to nie magia podpowiada tytuł, opis i obrazek — robią to znaczniki ",[61,123,124],{},"\u003Cmeta>"," w sekcji ",[61,127,128],{},"\u003Chead>"," strony:",[131,132,137],"pre",{"className":133,"code":134,"language":135,"meta":136,"style":136},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cmeta property=\"og:title\" content=\"Spójne grafiki bloga i obrazy OG\" \u002F>\n\u003Cmeta property=\"og:description\" content=\"Dwa SKILL-e Claude Code…\" \u002F>\n\u003Cmeta property=\"og:image\" content=\"https:\u002F\u002Fwww.awfs.dev\u002Fog\u002F0005.png\" \u002F>\n\u003Cmeta property=\"og:url\" content=\"https:\u002F\u002Fwww.awfs.dev\u002Fblog\u002F…\" \u002F>\n","html","",[61,138,139,183,214,245],{"__ignoreMap":136},[140,141,144,148,152,156,159,162,166,168,171,173,175,178,180],"span",{"class":142,"line":143},"line",1,[140,145,147],{"class":146},"sMK4o","\u003C",[140,149,151],{"class":150},"swJcz","meta",[140,153,155],{"class":154},"spNyl"," property",[140,157,158],{"class":146},"=",[140,160,161],{"class":146},"\"",[140,163,165],{"class":164},"sfazB","og:title",[140,167,161],{"class":146},[140,169,170],{"class":154}," content",[140,172,158],{"class":146},[140,174,161],{"class":146},[140,176,177],{"class":164},"Spójne grafiki bloga i obrazy OG",[140,179,161],{"class":146},[140,181,182],{"class":146}," \u002F>\n",[140,184,186,188,190,192,194,196,199,201,203,205,207,210,212],{"class":142,"line":185},2,[140,187,147],{"class":146},[140,189,151],{"class":150},[140,191,155],{"class":154},[140,193,158],{"class":146},[140,195,161],{"class":146},[140,197,198],{"class":164},"og:description",[140,200,161],{"class":146},[140,202,170],{"class":154},[140,204,158],{"class":146},[140,206,161],{"class":146},[140,208,209],{"class":164},"Dwa SKILL-e Claude Code…",[140,211,161],{"class":146},[140,213,182],{"class":146},[140,215,217,219,221,223,225,227,230,232,234,236,238,241,243],{"class":142,"line":216},3,[140,218,147],{"class":146},[140,220,151],{"class":150},[140,222,155],{"class":154},[140,224,158],{"class":146},[140,226,161],{"class":146},[140,228,229],{"class":164},"og:image",[140,231,161],{"class":146},[140,233,170],{"class":154},[140,235,158],{"class":146},[140,237,161],{"class":146},[140,239,240],{"class":164},"https:\u002F\u002Fwww.awfs.dev\u002Fog\u002F0005.png",[140,242,161],{"class":146},[140,244,182],{"class":146},[140,246,248,250,252,254,256,258,261,263,265,267,269,272,274],{"class":142,"line":247},4,[140,249,147],{"class":146},[140,251,151],{"class":150},[140,253,155],{"class":154},[140,255,158],{"class":146},[140,257,161],{"class":146},[140,259,260],{"class":164},"og:url",[140,262,161],{"class":146},[140,264,170],{"class":154},[140,266,158],{"class":146},[140,268,161],{"class":146},[140,270,271],{"class":164},"https:\u002F\u002Fwww.awfs.dev\u002Fblog\u002F…",[140,273,161],{"class":146},[140,275,182],{"class":146},[31,277,278,279,283,284,288,289,292,293,296],{},"Najważniejszy z perspektywy „klikalności” jest ",[41,280,281],{},[61,282,229],{}," — to ten duży obrazek w karcie podglądu. Dobrze dobrana grafika potrafi zauważalnie podnieść współczynnik kliknięć (",[285,286,287],"em",{},"CTR","), bo link przestaje być nagą linijką tekstu, a staje się ",[41,290,291],{},"kafelkiem przyciągającym wzrok",". Rekomendowany rozmiar to ",[41,294,295],{},"1200 × 630 px"," (proporcje 1.91:1) — i tego rozmiaru trzymam się wszędzie.",[31,298,299,300,303,304,64,307,310,311,314],{},"Warto wiedzieć, że OG ma „kuzyna” — ",[41,301,302],{},"Twitter Cards"," (",[61,305,306],{},"twitter:card",[61,308,309],{},"twitter:image","). W praktyce większość platform potrafi awaryjnie sięgnąć po znaczniki OG, więc poprawny zestaw ",[61,312,313],{},"og:*"," załatwia większość przypadków.",[26,316,318],{"id":317},"jak-sprawdzić-czy-strona-ma-poprawny-og","Jak sprawdzić, czy strona ma poprawny OG",[31,320,321],{},"Są dwa sposoby — ręczny i przez dedykowane narzędzia.",[31,323,324,327,328,331,332,335,336,339,340,343,344,347,348,351],{},[41,325,326],{},"Ręcznie."," Najprościej otworzyć ",[41,329,330],{},"źródło strony"," (w przeglądarce ",[61,333,334],{},"Ctrl\u002FCmd + U"," lub ",[285,337,338],{},"View Source",") i wyszukać ",[61,341,342],{},"og:",". Zobaczysz wszystkie znaczniki tak, jak trafiają do robotów. Z konsoli można to samo zrobić jednym poleceniem — używam ",[61,345,346],{},"grep -o"," z wzorcem na sam tag, bo HTML bywa zminifikowany do jednej linii i zwykły ",[61,349,350],{},"grep"," wypisałby wtedy całą stronę zamiast samych meta tagów:",[131,353,357],{"className":354,"code":355,"language":356,"meta":136,"style":136},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","curl -s https:\u002F\u002Fwww.awfs.dev\u002Fcontact | grep -o '\u003Cmeta[^>]*og:[^>]*>'\n","bash",[61,358,359],{"__ignoreMap":136},[140,360,361,365,368,371,374,377,380,383,386],{"class":142,"line":143},[140,362,364],{"class":363},"sBMFI","curl",[140,366,367],{"class":164}," -s",[140,369,370],{"class":164}," https:\u002F\u002Fwww.awfs.dev\u002Fcontact",[140,372,373],{"class":146}," |",[140,375,376],{"class":363}," grep",[140,378,379],{"class":164}," -o",[140,381,382],{"class":146}," '",[140,384,385],{"class":164},"\u003Cmeta[^>]*og:[^>]*>",[140,387,388],{"class":146},"'\n",[31,390,391,392,394,395,398,399,402,403,406,407,56],{},"Pamiętaj tylko, że ",[61,393,229],{}," powinien być ",[41,396,397],{},"adresem absolutnym"," (z ",[61,400,401],{},"https:\u002F\u002F…",") — część crawler-ów odrzuca względne ścieżki. Na stronie pilnuje tego helper ",[61,404,405],{},"absoluteUrl"," z ",[61,408,409],{},"app\u002Futils\u002Fseo.ts",[31,411,412,415],{},[41,413,414],{},"Narzędziami."," Wygodniej sprawdzić podgląd „oczami platformy”. Używam głównie:",[417,418,419,430,444],"ul",{},[420,421,422,429],"li",{},[41,423,424],{},[35,425,428],{"href":426,"rel":427},"https:\u002F\u002Fwww.opengraph.xyz",[111],"opengraph.xyz"," — pokazuje podgląd dla wielu serwisów naraz i wypisuje ostrzeżenia (np. zbyt krótki tytuł, zbyt długi opis, brak obrazu).",[420,431,432,439,440,443],{},[41,433,434],{},[35,435,438],{"href":436,"rel":437},"https:\u002F\u002Fwww.linkedin.com\u002Fpost-inspector\u002F",[111],"LinkedIn Post Inspector"," — kluczowy, bo publikuję głównie na LinkedIn; dodatkowo ",[41,441,442],{},"odświeża cache"," podglądu (platformy buforują OG i bez tego widzą starą wersję).",[420,445,446,453,454,457],{},[41,447,448],{},[35,449,452],{"href":450,"rel":451},"https:\u002F\u002Fdevelopers.facebook.com\u002Ftools\u002Fdebug\u002F",[111],"Meta Sharing Debugger"," i podgląd kart ",[41,455,456],{},"X"," — gdy chcę zweryfikować konkretną platformę.",[31,459,460,461,463,464,466,467,469,470,473,474,56],{},"To właśnie ostrzeżenia z ",[41,462,428],{}," (zbyt krótki ",[61,465,165],{},", zbyt długi ",[61,468,198],{},", brak ",[285,471,472],{},"call-to-action"," na obrazie) pchnęły mnie wcześniej do dopracowania metadanych podstron — ale to temat na osobną historię. Tutaj skupiam się na samych ",[41,475,476],{},"obrazach",[26,478,480],{"id":479},"pomysł-skill-dostaje-pomysł-skrypt-pilnuje-rygoru","Pomysł: SKILL dostaje pomysł, skrypt pilnuje rygoru",[31,482,483,484,487],{},"Generowanie obrazu przez model AI ma jedną zaletę i jedną wadę. Zaleta: modele potrafią narysować ładne, klimatyczne tło w dowolnym stylu. Wada: są ",[41,485,486],{},"niedeterministyczne"," — poproś dwa razy o to samo, dostaniesz dwa różne kadry, inne wymiary, a jeśli każesz dopisać tekst, to z dużym prawdopodobieństwem przekręci polskie znaki albo „wymyśli” litery.",[31,489,490,491,494],{},"Dlatego oba SKILL-e zbudowałem na ",[41,492,493],{},"wyraźnym podziale ról",":",[417,496,497,507],{},[420,498,499,502,503,506],{},[41,500,501],{},"Część kreatywna"," (tło, klimat, motyw) → ",[41,504,505],{},"model AI",". To jedyne, w czym model jest naprawdę dobry, a niedeterminizm tu nie przeszkadza.",[420,508,509,512,513,516],{},[41,510,511],{},"Część deterministyczna"," (dokładne wymiary, napisy, czcionka, logo, nazwa pliku) → ",[41,514,515],{},"skrypt pomocniczy"," w Node.js. Tu liczy się powtarzalność co do piksela.",[31,518,519,520,523,524,527,528,531,532,535],{},"W praktyce wygląda to tak, że SKILL (czyli ",[41,521,522],{},"Claude",") odpowiada tylko za ",[41,525,526],{},"brief"," — krótki opis sceny pasującej do treści — a całą resztę egzekwuje skrypt ",[61,529,530],{},".mjs",". Skrypt jest ",[41,533,534],{},"źródłem prawdy",": ma w sobie zaszyty stały prompt stylu, stałą czcionkę i stały rozmiar, więc agent nie ma jak „popłynąć”.",[26,537,539,540],{"id":538},"skill-1-grafiki-artykułów-generate-article-picture","SKILL #1: grafiki artykułów — ",[61,541,93],{},[31,543,544],{},"Pierwszy SKILL zlecam Claude'owi tak samo, jak wszystkie poprzednie — opisuję, czego chcę, a on przygotowuje gotowego SKILL-a wraz ze skryptem. Mój prompt brzmiał:",[131,546,550],{"className":547,"code":548,"language":549,"meta":136,"style":136},"language-markdown shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","Przygotuj SKILL o nazwie **generate-article-picture** wraz ze skryptem pomocniczym:\n\n- SKILL przyjmuje jeden parametr, który jest _slug-iem_ artykułu, którego plik MD znajduje się w folderze `.\u002Fcontent\u002Fblog\u002F`.\n- Rolą jest przygotowanie obrazu o zadanym stylu i pasującego do treści artykułu.\n- Czyta artykuł i przygotowuje streszczenie (opis czego ma dotyczyć obraz) dla modelu AI do generowania obrazów.\n- Obraz musi być DOKŁADNIE o wymiarach **1200 x 630 pikseli**.\n- Obraz musi być w stylu nowoczesnym, nawet futurystycznym, związany z programowaniem i technologiami AI.\n- Obraz musi być w ciemniejszych kolorach.\n- Jedyne napisy na obrazie to:\n  - Numer artykułu w formie **#N**, gdzie `N` to wartość z pola `order` metadanych, kolor jasno szary, półprzeźroczysty, prawy dolny róg obrazu (dla 4 to #4).\n  - Tytuł artykułu w formie, czyli wartość z pola `title` metadanych, kolor ciemniejszy szary, półprzeźroczysty, prawy dolny róg nad **#N**.\n  - Zawsze ta sama czcionka dla wszystkich obrazów generowanych dla różnych artykułów.\n- Prompt z wszystkimi wytycznymi dla modelu AI wysyłany jest przez portal _OpenRouter_:\n  - Domyślny model: `google\u002Fgemini-3.1-flash-image-preview`.\n  - Model i klucz API konfigurowane w pliku `.env`.\n- Wygenerowany obraz trafia do folderu `.\u002Fpublic\u002Fpics\u002F`, do pliku `NNNN.png`, gdzie `NNNN` to numer z pola `order` artykułu z zerami wiodącymi (dla 4 to 0004.png).\n","markdown",[61,551,552,570,575,607,614,622,639,647,655,663,702,728,736,754,771,788],{"__ignoreMap":136},[140,553,554,558,562,565,567],{"class":142,"line":143},[140,555,557],{"class":556},"sTEyZ","Przygotuj SKILL o nazwie ",[140,559,561],{"class":560},"sHepR","**",[140,563,93],{"class":564},"so75L",[140,566,561],{"class":560},[140,568,569],{"class":556}," wraz ze skryptem pomocniczym:\n",[140,571,572],{"class":142,"line":185},[140,573,574],{"emptyLinePlaceholder":10},"\n",[140,576,577,580,583,587,591,593,596,599,602,604],{"class":142,"line":216},[140,578,579],{"class":146},"-",[140,581,582],{"class":556}," SKILL przyjmuje jeden parametr, który jest ",[140,584,586],{"class":585},"s7zQu","_",[140,588,590],{"class":589},"s5tWE","slug-iem",[140,592,586],{"class":585},[140,594,595],{"class":556}," artykułu, którego plik MD znajduje się w folderze ",[140,597,598],{"class":146},"`",[140,600,601],{"class":164},".\u002Fcontent\u002Fblog\u002F",[140,603,598],{"class":146},[140,605,606],{"class":556},".\n",[140,608,609,611],{"class":142,"line":247},[140,610,579],{"class":146},[140,612,613],{"class":556}," Rolą jest przygotowanie obrazu o zadanym stylu i pasującego do treści artykułu.\n",[140,615,617,619],{"class":142,"line":616},5,[140,618,579],{"class":146},[140,620,621],{"class":556}," Czyta artykuł i przygotowuje streszczenie (opis czego ma dotyczyć obraz) dla modelu AI do generowania obrazów.\n",[140,623,625,627,630,632,635,637],{"class":142,"line":624},6,[140,626,579],{"class":146},[140,628,629],{"class":556}," Obraz musi być DOKŁADNIE o wymiarach ",[140,631,561],{"class":560},[140,633,634],{"class":564},"1200 x 630 pikseli",[140,636,561],{"class":560},[140,638,606],{"class":556},[140,640,642,644],{"class":142,"line":641},7,[140,643,579],{"class":146},[140,645,646],{"class":556}," Obraz musi być w stylu nowoczesnym, nawet futurystycznym, związany z programowaniem i technologiami AI.\n",[140,648,650,652],{"class":142,"line":649},8,[140,651,579],{"class":146},[140,653,654],{"class":556}," Obraz musi być w ciemniejszych kolorach.\n",[140,656,658,660],{"class":142,"line":657},9,[140,659,579],{"class":146},[140,661,662],{"class":556}," Jedyne napisy na obrazie to:\n",[140,664,666,669,672,674,677,679,682,684,687,689,692,694,697,699],{"class":142,"line":665},10,[140,667,668],{"class":146},"  -",[140,670,671],{"class":556}," Numer artykułu w formie ",[140,673,561],{"class":560},[140,675,676],{"class":564},"#N",[140,678,561],{"class":560},[140,680,681],{"class":556},", gdzie ",[140,683,598],{"class":146},[140,685,686],{"class":164},"N",[140,688,598],{"class":146},[140,690,691],{"class":556}," to wartość z pola ",[140,693,598],{"class":146},[140,695,696],{"class":164},"order",[140,698,598],{"class":146},[140,700,701],{"class":556}," metadanych, kolor jasno szary, półprzeźroczysty, prawy dolny róg obrazu (dla 4 to #4).\n",[140,703,705,707,710,712,715,717,720,722,724,726],{"class":142,"line":704},11,[140,706,668],{"class":146},[140,708,709],{"class":556}," Tytuł artykułu w formie, czyli wartość z pola ",[140,711,598],{"class":146},[140,713,714],{"class":164},"title",[140,716,598],{"class":146},[140,718,719],{"class":556}," metadanych, kolor ciemniejszy szary, półprzeźroczysty, prawy dolny róg nad ",[140,721,561],{"class":560},[140,723,676],{"class":564},[140,725,561],{"class":560},[140,727,606],{"class":556},[140,729,731,733],{"class":142,"line":730},12,[140,732,668],{"class":146},[140,734,735],{"class":556}," Zawsze ta sama czcionka dla wszystkich obrazów generowanych dla różnych artykułów.\n",[140,737,739,741,744,746,749,751],{"class":142,"line":738},13,[140,740,579],{"class":146},[140,742,743],{"class":556}," Prompt z wszystkimi wytycznymi dla modelu AI wysyłany jest przez portal ",[140,745,586],{"class":585},[140,747,748],{"class":589},"OpenRouter",[140,750,586],{"class":585},[140,752,753],{"class":556},":\n",[140,755,757,759,762,764,767,769],{"class":142,"line":756},14,[140,758,668],{"class":146},[140,760,761],{"class":556}," Domyślny model: ",[140,763,598],{"class":146},[140,765,766],{"class":164},"google\u002Fgemini-3.1-flash-image-preview",[140,768,598],{"class":146},[140,770,606],{"class":556},[140,772,774,776,779,781,784,786],{"class":142,"line":773},15,[140,775,668],{"class":146},[140,777,778],{"class":556}," Model i klucz API konfigurowane w pliku ",[140,780,598],{"class":146},[140,782,783],{"class":164},".env",[140,785,598],{"class":146},[140,787,606],{"class":556},[140,789,791,793,796,798,801,803,806,808,811,813,815,817,820,822,825,827,829,831],{"class":142,"line":790},16,[140,792,579],{"class":146},[140,794,795],{"class":556}," Wygenerowany obraz trafia do folderu ",[140,797,598],{"class":146},[140,799,800],{"class":164},".\u002Fpublic\u002Fpics\u002F",[140,802,598],{"class":146},[140,804,805],{"class":556},", do pliku ",[140,807,598],{"class":146},[140,809,810],{"class":164},"NNNN.png",[140,812,598],{"class":146},[140,814,681],{"class":556},[140,816,598],{"class":146},[140,818,819],{"class":164},"NNNN",[140,821,598],{"class":146},[140,823,824],{"class":556}," to numer z pola ",[140,826,598],{"class":146},[140,828,696],{"class":164},[140,830,598],{"class":146},[140,832,833],{"class":556}," artykułu z zerami wiodącymi (dla 4 to 0004.png).\n",[31,835,836,837,840],{},"Pierwsze efekty były technicznie poprawne, ale zbyt ",[41,838,839],{},"ponure"," — same szarości. Wystarczyła jedna korekta promptu, żeby ożywić paletę:",[131,842,844],{"className":547,"code":843,"language":549,"meta":136,"style":136},"Generowane obrazy są dość ponure, mają być w ciemniejszych barwach,\nale niekoniecznie tylko odcienie szarości. Mogą być bardziej pozytywne.\n",[61,845,846,851],{"__ignoreMap":136},[140,847,848],{"class":142,"line":143},[140,849,850],{"class":556},"Generowane obrazy są dość ponure, mają być w ciemniejszych barwach,\n",[140,852,853],{"class":142,"line":185},[140,854,855],{"class":556},"ale niekoniecznie tylko odcienie szarości. Mogą być bardziej pozytywne.\n",[31,857,858,859,862],{},"Od tej pory bazą jest głęboka ciemność (granat, grafit, czerń), ale ",[41,860,861],{},"rozświetlona żywymi akcentami"," — turkus, zieleń, magenta, bursztyn, coral. Ciemno, ale nie ponuro.",[864,865,867],"h3",{"id":866},"rola-ai-i-sekret-spójnego-stylu","Rola AI i sekret spójnego stylu",[31,869,870,871,874,875,878,879,882,883,494],{},"Mogłoby się wydawać, że spójność stylu zależy od modelu. Nie zależy. ",[41,872,873],{},"Spójność bierze się ze stałego promptu zaszytego w skrypcie",", a nie z briefu, który zmienia się przy każdym artykule. W skrypcie znajduje się sekcja ",[61,876,877],{},"STYLE"," (ten sam klimat dla całej serii) i ",[61,880,881],{},"STRICT CONSTRAINTS",", a brief od Claude'a dokleja się tylko jako sekcja ",[61,884,885],{},"SCENE",[131,887,892],{"className":888,"code":890,"language":891,"meta":136},[889],"language-text","STYLE (identical mood for every article in this series):\n- Modern, even futuristic; theme: software development and AI technologies.\n- Dark but NOT gloomy … vibrant, optimistic accent colors …\n\nSTRICT CONSTRAINTS:\n- Do NOT render ANY text, letters, numbers, words, captions, logos …\n- Keep the LOWER-RIGHT quadrant calmer and darker … so caption text stays legible.\n\nSCENE (what this specific article is about):\n\u003Cbrief od Claude'a, np. „a pipeline turning AI-generated artwork into branded social cards\">\n","text",[61,893,890],{"__ignoreMap":136},[31,895,896,897,899,900,903,904,907],{},"Zwróć uwagę na dwie rzeczy w ",[61,898,881],{},". Po pierwsze: model ma ",[41,901,902],{},"kategoryczny zakaz rysowania jakiegokolwiek tekstu"," — bo napisy nałoży skrypt. Po drugie: proszę model, żeby ",[41,905,906],{},"prawy dolny róg był ciemniejszy i spokojniejszy"," — bo właśnie tam ląduje tytuł i numer artykułu, które muszą pozostać czytelne.",[31,909,910,911,914],{},"Rolą Claude'a jest więc tylko jedno: przeczytać artykuł i napisać ",[41,912,913],{},"2–4 zdania po angielsku"," opisujące scenę. Cała tożsamość wizualna serii jest poza modelem.",[864,916,918],{"id":917},"co-dzieje-się-po-wygenerowaniu-obrazu","Co dzieje się po wygenerowaniu obrazu",[31,920,921,922,925,926,494],{},"Surowy obraz z modelu to dopiero półprodukt. Reszta to ",[41,923,924],{},"deterministyczny post-processing"," w Node.js przy użyciu biblioteki ",[41,927,928],{},[35,929,932],{"href":930,"rel":931},"https:\u002F\u002Fsharp.pixelplumbing.com\u002F",[111],"sharp",[934,935,936,953],"ol",{},[420,937,938,941,942,944,945,948,949,952],{},[41,939,940],{},"Przycięcie do dokładnych wymiarów."," Model bywa kapryśny co do proporcji, więc ",[61,943,932],{}," skaluje wynik do ",[41,946,947],{},"1200 × 630"," przez ",[61,950,951],{},"resize(WIDTH, HEIGHT, { fit: 'cover' })",". To gwarantuje, że każdy obraz w serii ma identyczny rozmiar.",[420,954,955,958,959,961,962,965,966,969],{},[41,956,957],{},"Nałożenie napisów."," Tytuł i numer ",[61,960,676],{}," renderuję jako ",[41,963,964],{},"nakładkę SVG"," złożoną na obraz przez ",[61,967,968],{},"sharp.composite()",". Tekst w SVG jest „twardy” — wpisany wprost w XML, więc nie ma mowy o przekręceniu liter.",[31,971,972],{},"Dlaczego napisy renderuje skrypt, a nie AI? Trzy powody, wszystkie praktyczne:",[417,974,975,985,994],{},[420,976,977,980,981,984],{},[41,978,979],{},"Poprawne polskie diakrytyki."," Skrypt używa stałej czcionki ",[41,982,983],{},"DejaVu Sans",", która ma pełny komplet polskich znaków. Model AI regularnie gubi „ł”, „ż” czy „ą” albo renderuje je jako artefakty.",[420,986,987,990,991,993],{},[41,988,989],{},"Powtarzalność co do piksela."," Tytuł zawsze tym samym fontem, w tym samym kolorze (ciemniejszy szary, półprzeźroczysty), w prawym dolnym rogu, z numerem ",[61,992,676],{}," pod spodem. Żadnych wahań między artykułami.",[420,995,996,999,1000,1003,1004,1007,1008,64,1010,1012],{},[41,997,998],{},"Determinizm nazwy i treści."," Nazwa pliku (",[61,1001,1002],{},"0005.png",") i treść napisów pochodzą wprost z ",[41,1005,1006],{},"metadanych"," artykułu (",[61,1009,714],{},[61,1011,696],{},") — nie ma tu miejsca na kreatywność modelu.",[31,1014,1015],{},"W kodzie wygląda to mniej więcej tak (uproszczony fragment skryptu):",[131,1017,1021],{"className":1018,"code":1019,"language":1020,"meta":136,"style":136},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const background = await sharp(rawImage)\n  .resize(1200, 630, { fit: 'cover', position: 'centre' })\n  .toBuffer()\n\nconst finalPng = await sharp(background)\n  .composite([{ input: buildOverlaySvg(title, order), top: 0, left: 0 }])\n  .png()\n  .toBuffer()\n","js",[61,1022,1023,1043,1102,1112,1116,1132,1185,1194],{"__ignoreMap":136},[140,1024,1025,1028,1031,1033,1036,1040],{"class":142,"line":143},[140,1026,1027],{"class":154},"const",[140,1029,1030],{"class":556}," background ",[140,1032,158],{"class":146},[140,1034,1035],{"class":585}," await",[140,1037,1039],{"class":1038},"s2Zo4"," sharp",[140,1041,1042],{"class":556},"(rawImage)\n",[140,1044,1045,1048,1051,1054,1058,1061,1064,1066,1069,1072,1074,1076,1079,1082,1084,1087,1089,1091,1094,1096,1099],{"class":142,"line":185},[140,1046,1047],{"class":146},"  .",[140,1049,1050],{"class":1038},"resize",[140,1052,1053],{"class":556},"(",[140,1055,1057],{"class":1056},"sbssI","1200",[140,1059,1060],{"class":146},",",[140,1062,1063],{"class":1056}," 630",[140,1065,1060],{"class":146},[140,1067,1068],{"class":146}," {",[140,1070,1071],{"class":150}," fit",[140,1073,494],{"class":146},[140,1075,382],{"class":146},[140,1077,1078],{"class":164},"cover",[140,1080,1081],{"class":146},"'",[140,1083,1060],{"class":146},[140,1085,1086],{"class":150}," position",[140,1088,494],{"class":146},[140,1090,382],{"class":146},[140,1092,1093],{"class":164},"centre",[140,1095,1081],{"class":146},[140,1097,1098],{"class":146}," }",[140,1100,1101],{"class":556},")\n",[140,1103,1104,1106,1109],{"class":142,"line":216},[140,1105,1047],{"class":146},[140,1107,1108],{"class":1038},"toBuffer",[140,1110,1111],{"class":556},"()\n",[140,1113,1114],{"class":142,"line":247},[140,1115,574],{"emptyLinePlaceholder":10},[140,1117,1118,1120,1123,1125,1127,1129],{"class":142,"line":616},[140,1119,1027],{"class":154},[140,1121,1122],{"class":556}," finalPng ",[140,1124,158],{"class":146},[140,1126,1035],{"class":585},[140,1128,1039],{"class":1038},[140,1130,1131],{"class":556},"(background)\n",[140,1133,1134,1136,1139,1142,1145,1148,1150,1153,1156,1158,1161,1163,1166,1168,1171,1173,1176,1178,1180,1182],{"class":142,"line":624},[140,1135,1047],{"class":146},[140,1137,1138],{"class":1038},"composite",[140,1140,1141],{"class":556},"([",[140,1143,1144],{"class":146},"{",[140,1146,1147],{"class":150}," input",[140,1149,494],{"class":146},[140,1151,1152],{"class":1038}," buildOverlaySvg",[140,1154,1155],{"class":556},"(title",[140,1157,1060],{"class":146},[140,1159,1160],{"class":556}," order)",[140,1162,1060],{"class":146},[140,1164,1165],{"class":150}," top",[140,1167,494],{"class":146},[140,1169,1170],{"class":1056}," 0",[140,1172,1060],{"class":146},[140,1174,1175],{"class":150}," left",[140,1177,494],{"class":146},[140,1179,1170],{"class":1056},[140,1181,1098],{"class":146},[140,1183,1184],{"class":556},"])\n",[140,1186,1187,1189,1192],{"class":142,"line":641},[140,1188,1047],{"class":146},[140,1190,1191],{"class":1038},"png",[140,1193,1111],{"class":556},[140,1195,1196,1198,1200],{"class":142,"line":649},[140,1197,1047],{"class":146},[140,1199,1108],{"class":1038},[140,1201,1111],{"class":556},[31,1203,1204,1205,1211,1212,1214,1215,1218,1219,1221,1222,1224,1225,1228],{},"Sam obraz powstaje przez ",[41,1206,1207],{},[35,1208,748],{"href":1209,"rel":1210},"https:\u002F\u002Fopenrouter.ai\u002F",[111]," (domyślny model ",[61,1213,766],{}," - ",[285,1216,1217],{},"Nano Banana 2","), a klucz API i model siedzą wyłącznie w pliku ",[61,1220,783],{}," — nigdy w repozytorium. Koszt jest niewielki: jedno wygenerowanie obrazu domyślnym modelem ",[285,1223,1217],{}," to około ",[41,1226,1227],{},"$0.07",", więc nawet kilka podejść do briefu mieści się w rozsądnych kwotach.",[26,1230,1232,1233],{"id":1231},"skill-2-obrazy-og-podstron-generate-page-og-picture","SKILL #2: obrazy OG podstron — ",[61,1234,98],{},[31,1236,1237,1238,1241,1242,1245,1246,1249,1250,1253],{},"Drugi SKILL działa na tej samej zasadzie, ale dla ",[41,1239,1240],{},"podstron",", nie artykułów. Zlecając go, dorzuciłem ważną decyzję projektową — chciałem ",[41,1243,1244],{},"wyłączyć generowanie OG w czasie build-u"," (moduł ",[61,1247,1248],{},"nuxt-og-image"," dawał niezadowalające efekty) i zastąpić je ",[41,1251,1252],{},"statycznymi obrazami"," tworzonymi raz, w czasie development-u. Fragment promptu:",[131,1255,1257],{"className":547,"code":1256,"language":549,"meta":136,"style":136},"1. Całkowicie wyłącz aktualny mechanizm generowania grafik OG w czasie build.\n   Chcę mieć statyczne obrazy generowane w czasie development-u.\n\n2. Przygotuj SKILL o **nazwie generate-page-og-picture** wraz ze skryptem:\n\n- Analogicznie do SKILL-a **generate-article-picture**.\n- SKILL przyjmuje dwa parametry:\n  - URL względny strony, np.: `\u002Fcontact`.\n  - Główny tekst grafiki OG.\n- Rolą jest przygotowanie obrazu typu _OG_ o zadanym stylu.\n- Obraz musi być DOKŁADNIE o wymiarach **1200 x 630 pikseli**.\n- Obraz musi mieć tło w kolorze tła strony w trybie ciemnym.\n- Grafika obrazu to wygenerowane przez AI delikatne kształty geometryczne w stylu nowoczesnym, nawet futurystycznym.\n- W lewym górny rogu grafiki musi być logo strony + napis AWFS.dev - identycznie jak jest z lewej strony górnej belki strony.\n- Na środku tekst podany jak drugi argument SKILL-a. Jeśli użyto znaku \\n to rozbij tekst na linie.\n- Zawsze ta sama czcionka dla wszystkich obrazów generowanych tym SKILL-em.\n- Prompt z wszystkimi wytycznymi dla modelu AI wysyłany jest przez portal _OpenRouter_:\n  - Domyślny model: `google\u002Fgemini-3.1-flash-image-preview` (Nano Banana 2).\n  - Model i klucz API konfigurowane w pliku `.env`.\n- Wygenerowany obraz trafia do folderu `.\u002Fpublic\u002Fog\u002F`, do pliku o nazwie strony, np.: `contact.png` dla strony `\u002Fcontact` (do nazwy bierz ostatni człon URL, czyli `test2.png` dla strony `\u002Ftest1\u002Ftest2`).\n- Zaktualizuj meta dane strony tak, żeby `og:image` wskazywał na nowo utworzony obraz.\n",[61,1258,1259,1267,1272,1276,1294,1298,1313,1320,1335,1342,1358,1372,1379,1386,1393,1400,1407,1422,1438,1453,1508],{"__ignoreMap":136},[140,1260,1261,1264],{"class":142,"line":143},[140,1262,1263],{"class":146},"1.",[140,1265,1266],{"class":556}," Całkowicie wyłącz aktualny mechanizm generowania grafik OG w czasie build.\n",[140,1268,1269],{"class":142,"line":185},[140,1270,1271],{"class":556},"   Chcę mieć statyczne obrazy generowane w czasie development-u.\n",[140,1273,1274],{"class":142,"line":216},[140,1275,574],{"emptyLinePlaceholder":10},[140,1277,1278,1281,1284,1286,1289,1291],{"class":142,"line":247},[140,1279,1280],{"class":146},"2.",[140,1282,1283],{"class":556}," Przygotuj SKILL o ",[140,1285,561],{"class":560},[140,1287,1288],{"class":564},"nazwie generate-page-og-picture",[140,1290,561],{"class":560},[140,1292,1293],{"class":556}," wraz ze skryptem:\n",[140,1295,1296],{"class":142,"line":616},[140,1297,574],{"emptyLinePlaceholder":10},[140,1299,1300,1302,1305,1307,1309,1311],{"class":142,"line":624},[140,1301,579],{"class":146},[140,1303,1304],{"class":556}," Analogicznie do SKILL-a ",[140,1306,561],{"class":560},[140,1308,93],{"class":564},[140,1310,561],{"class":560},[140,1312,606],{"class":556},[140,1314,1315,1317],{"class":142,"line":641},[140,1316,579],{"class":146},[140,1318,1319],{"class":556}," SKILL przyjmuje dwa parametry:\n",[140,1321,1322,1324,1327,1329,1331,1333],{"class":142,"line":649},[140,1323,668],{"class":146},[140,1325,1326],{"class":556}," URL względny strony, np.: ",[140,1328,598],{"class":146},[140,1330,70],{"class":164},[140,1332,598],{"class":146},[140,1334,606],{"class":556},[140,1336,1337,1339],{"class":142,"line":657},[140,1338,668],{"class":146},[140,1340,1341],{"class":556}," Główny tekst grafiki OG.\n",[140,1343,1344,1346,1349,1351,1353,1355],{"class":142,"line":665},[140,1345,579],{"class":146},[140,1347,1348],{"class":556}," Rolą jest przygotowanie obrazu typu ",[140,1350,586],{"class":585},[140,1352,116],{"class":589},[140,1354,586],{"class":585},[140,1356,1357],{"class":556}," o zadanym stylu.\n",[140,1359,1360,1362,1364,1366,1368,1370],{"class":142,"line":704},[140,1361,579],{"class":146},[140,1363,629],{"class":556},[140,1365,561],{"class":560},[140,1367,634],{"class":564},[140,1369,561],{"class":560},[140,1371,606],{"class":556},[140,1373,1374,1376],{"class":142,"line":730},[140,1375,579],{"class":146},[140,1377,1378],{"class":556}," Obraz musi mieć tło w kolorze tła strony w trybie ciemnym.\n",[140,1380,1381,1383],{"class":142,"line":738},[140,1382,579],{"class":146},[140,1384,1385],{"class":556}," Grafika obrazu to wygenerowane przez AI delikatne kształty geometryczne w stylu nowoczesnym, nawet futurystycznym.\n",[140,1387,1388,1390],{"class":142,"line":756},[140,1389,579],{"class":146},[140,1391,1392],{"class":556}," W lewym górny rogu grafiki musi być logo strony + napis AWFS.dev - identycznie jak jest z lewej strony górnej belki strony.\n",[140,1394,1395,1397],{"class":142,"line":773},[140,1396,579],{"class":146},[140,1398,1399],{"class":556}," Na środku tekst podany jak drugi argument SKILL-a. Jeśli użyto znaku \\n to rozbij tekst na linie.\n",[140,1401,1402,1404],{"class":142,"line":790},[140,1403,579],{"class":146},[140,1405,1406],{"class":556}," Zawsze ta sama czcionka dla wszystkich obrazów generowanych tym SKILL-em.\n",[140,1408,1410,1412,1414,1416,1418,1420],{"class":142,"line":1409},17,[140,1411,579],{"class":146},[140,1413,743],{"class":556},[140,1415,586],{"class":585},[140,1417,748],{"class":589},[140,1419,586],{"class":585},[140,1421,753],{"class":556},[140,1423,1425,1427,1429,1431,1433,1435],{"class":142,"line":1424},18,[140,1426,668],{"class":146},[140,1428,761],{"class":556},[140,1430,598],{"class":146},[140,1432,766],{"class":164},[140,1434,598],{"class":146},[140,1436,1437],{"class":556}," (Nano Banana 2).\n",[140,1439,1441,1443,1445,1447,1449,1451],{"class":142,"line":1440},19,[140,1442,668],{"class":146},[140,1444,778],{"class":556},[140,1446,598],{"class":146},[140,1448,783],{"class":164},[140,1450,598],{"class":146},[140,1452,606],{"class":556},[140,1454,1456,1458,1460,1462,1465,1467,1470,1472,1475,1477,1480,1482,1484,1486,1489,1491,1494,1496,1498,1500,1503,1505],{"class":142,"line":1455},20,[140,1457,579],{"class":146},[140,1459,795],{"class":556},[140,1461,598],{"class":146},[140,1463,1464],{"class":164},".\u002Fpublic\u002Fog\u002F",[140,1466,598],{"class":146},[140,1468,1469],{"class":556},", do pliku o nazwie strony, np.: ",[140,1471,598],{"class":146},[140,1473,1474],{"class":164},"contact.png",[140,1476,598],{"class":146},[140,1478,1479],{"class":556}," dla strony ",[140,1481,598],{"class":146},[140,1483,70],{"class":164},[140,1485,598],{"class":146},[140,1487,1488],{"class":556}," (do nazwy bierz ostatni człon URL, czyli ",[140,1490,598],{"class":146},[140,1492,1493],{"class":164},"test2.png",[140,1495,598],{"class":146},[140,1497,1479],{"class":556},[140,1499,598],{"class":146},[140,1501,1502],{"class":164},"\u002Ftest1\u002Ftest2",[140,1504,598],{"class":146},[140,1506,1507],{"class":556},").\n",[140,1509,1511,1513,1516,1518,1520,1522],{"class":142,"line":1510},21,[140,1512,579],{"class":146},[140,1514,1515],{"class":556}," Zaktualizuj meta dane strony tak, żeby ",[140,1517,598],{"class":146},[140,1519,229],{"class":164},[140,1521,598],{"class":146},[140,1523,1524],{"class":556}," wskazywał na nowo utworzony obraz.\n",[31,1526,1527,1528,303,1531,1534,1535,1538,1539,1542],{},"Różnice względem grafik artykułów wynikają z innego celu. Tu tłem jest ",[41,1529,1530],{},"dokładny kolor strony w trybie ciemnym",[61,1532,1533],{},"#111827","), a model AI dorysowuje na nim tylko ",[41,1536,1537],{},"delikatne, geometryczne kształty"," (cienkie linie, wireframe, subtelne poświaty) — stonowane, bo nad nimi ląduje tekst i marka. Krycie tej warstwy AI reguluję opcją ",[61,1540,1541],{},"--opacity"," (domyślnie 0.7), więc kształty nigdy nie przebijają treści.",[31,1544,1545,1546,1548],{},"Tak wygląda gotowy efekt — obraz OG strony ",[61,1547,70],{}," wygenerowany tym SKILL-em: ciemne tło strony, subtelna geometryczna siatka od AI, logo „AWFS.dev” w lewym górnym rogu i wycentrowany tekst nałożony deterministycznie przez skrypt:",[31,1550,1551],{},[1552,1553],"img",{"alt":1554,"src":1555},"Obraz OG strony \u002Fcontact: ciemne tło z delikatną geometryczną siatką, logo AWFS.dev w lewym górnym rogu i wycentrowany napis „Kontakt”","\u002Fog\u002Fcontact.png",[31,1557,1558,1559,1562],{},"Najważniejszy element to ",[41,1560,1561],{},"logo AWFS.dev w lewym górnym rogu"," — odwzorowanie górnej belki strony. I tu pojawia się ciekawy szczegół architektoniczny.",[864,1564,1566],{"id":1565},"jedno-źródło-prawdy-dla-logo","Jedno źródło prawdy dla logo",[31,1568,1569,1570,1573,1574,1579,1580,1583,1584,1587,1588,1591,1592,1597,1598,1600],{},"Logo musi wyglądać ",[41,1571,1572],{},"identycznie"," na każdym obrazie OG. Żeby to wymusić, wydzieliłem je do współdzielonego modułu ",[41,1575,1576],{},[61,1577,1578],{},"og-logo.mjs"," — jedynego źródła prawdy dla marki. Moduł odwzorowuje komponent ",[61,1581,1582],{},"AppLogo.vue"," co do koloru: box ",[61,1585,1586],{},"gray-800",", „AW”\u002F„dev” szare, „FS” błękitne (",[61,1589,1590],{},"sky","), kropka żółta. Sama ikona to natywny ",[41,1593,1594],{},[61,1595,1596],{},"public\u002Ficon_32x32.png",", dokładany osobno przez ",[61,1599,932],{}," (a nie rysowany w SVG), żeby zachować ostrość:",[131,1602,1604],{"className":1018,"code":1603,"language":1020,"meta":136,"style":136},"return [\n  { input: buildLogoSvg(), top: 0, left: 0 }, \u002F\u002F box + napis \"AWFS.dev\"\n  { input: icon, top: MARGIN + ICON_PAD, left: MARGIN + ICON_PAD } \u002F\u002F ikona PNG\n]\n",[61,1605,1606,1614,1652,1697],{"__ignoreMap":136},[140,1607,1608,1611],{"class":142,"line":143},[140,1609,1610],{"class":585},"return",[140,1612,1613],{"class":556}," [\n",[140,1615,1616,1619,1621,1623,1626,1629,1631,1633,1635,1637,1639,1641,1643,1645,1648],{"class":142,"line":185},[140,1617,1618],{"class":146},"  {",[140,1620,1147],{"class":150},[140,1622,494],{"class":146},[140,1624,1625],{"class":1038}," buildLogoSvg",[140,1627,1628],{"class":556},"()",[140,1630,1060],{"class":146},[140,1632,1165],{"class":150},[140,1634,494],{"class":146},[140,1636,1170],{"class":1056},[140,1638,1060],{"class":146},[140,1640,1175],{"class":150},[140,1642,494],{"class":146},[140,1644,1170],{"class":1056},[140,1646,1647],{"class":146}," },",[140,1649,1651],{"class":1650},"sHwdD"," \u002F\u002F box + napis \"AWFS.dev\"\n",[140,1653,1654,1656,1658,1660,1663,1665,1667,1669,1672,1675,1678,1680,1682,1684,1686,1688,1691,1694],{"class":142,"line":216},[140,1655,1618],{"class":146},[140,1657,1147],{"class":150},[140,1659,494],{"class":146},[140,1661,1662],{"class":556}," icon",[140,1664,1060],{"class":146},[140,1666,1165],{"class":150},[140,1668,494],{"class":146},[140,1670,1671],{"class":556}," MARGIN ",[140,1673,1674],{"class":146},"+",[140,1676,1677],{"class":556}," ICON_PAD",[140,1679,1060],{"class":146},[140,1681,1175],{"class":150},[140,1683,494],{"class":146},[140,1685,1671],{"class":556},[140,1687,1674],{"class":146},[140,1689,1690],{"class":556}," ICON_PAD ",[140,1692,1693],{"class":146},"}",[140,1695,1696],{"class":1650}," \u002F\u002F ikona PNG\n",[140,1698,1699],{"class":142,"line":247},[140,1700,1701],{"class":556},"]\n",[31,1703,1704,1705,1707,1708,56],{},"Dzięki temu, że ",[61,1706,1578],{}," jest importowany w kilku miejscach, ",[41,1709,1710],{},"zmiana wyglądu logo w jednym pliku propaguje się wszędzie",[864,1712,1714],{"id":1713},"grafiki-artykułów-też-stają-się-obrazami-og","Grafiki artykułów też stają się obrazami OG",[31,1716,1717,1718,1721,1722,56],{},"Zostało jeszcze jedno: artykuły bloga jako linki też powinny ładnie wyglądać w podglądzie — i to z ",[41,1719,1720],{},"brandingiem AWFS",". Nie chciałem jednak generować dla nich osobnych obrazów. Rozwiązanie: wziąć istniejącą grafikę nagłówkową artykułu i ",[41,1723,1724],{},"doklejać do niej tylko logo",[31,1726,1727,1728,1733,1734,1736,1737,1740],{},"Służy do tego trzeci skrypt — ",[41,1729,1730],{},[61,1731,1732],{},"stamp-og-logo.mjs"," — który korzysta z tego samego ",[61,1735,1578],{},", ale nakłada ",[41,1738,1739],{},"samo logo"," (bez AI i bez tekstu środkowego) na gotowy obraz, skalując go do 1200 × 630:",[131,1742,1744],{"className":354,"code":1743,"language":356,"meta":136,"style":136},"node .claude\u002Fskills\u002Fgenerate-page-og-picture\u002Fstamp-og-logo.mjs public\u002Fog\u002F0005.png\n",[61,1745,1746],{"__ignoreMap":136},[140,1747,1748,1751,1754],{"class":142,"line":143},[140,1749,1750],{"class":363},"node",[140,1752,1753],{"class":164}," .claude\u002Fskills\u002Fgenerate-page-og-picture\u002Fstamp-og-logo.mjs",[140,1755,1756],{"class":164}," public\u002Fog\u002F0005.png\n",[31,1758,1759,1760,1763,1764,1767,1768,1770,1771,1774,1775,494],{},"Grafiki artykułów kopiuję więc z ",[61,1761,1762],{},"public\u002Fpics\u002FNNNN.png"," do ",[61,1765,1766],{},"public\u002Fog\u002FNNNN.png",", stempluję logiem, a strona artykułu mapuje ",[61,1769,229],{}," prostą podmianą prefiksu ścieżki — ",[61,1772,1773],{},"\u002Fpics\u002F"," → ",[61,1776,1777],{},"\u002Fog\u002F",[131,1779,1783],{"className":1780,"code":1781,"language":1782,"meta":136,"style":136},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const ogImage = page.value.image\n  ? absoluteUrl(page.value.image.replace('\u002Fpics\u002F', '\u002Fog\u002F'))\n  : undefined\n","ts",[61,1784,1785,1807,1851],{"__ignoreMap":136},[140,1786,1787,1789,1792,1794,1797,1799,1802,1804],{"class":142,"line":143},[140,1788,1027],{"class":154},[140,1790,1791],{"class":556}," ogImage ",[140,1793,158],{"class":146},[140,1795,1796],{"class":556}," page",[140,1798,56],{"class":146},[140,1800,1801],{"class":556},"value",[140,1803,56],{"class":146},[140,1805,1806],{"class":556},"image\n",[140,1808,1809,1812,1815,1818,1820,1822,1824,1827,1829,1832,1834,1836,1838,1840,1842,1844,1846,1848],{"class":142,"line":185},[140,1810,1811],{"class":146},"  ?",[140,1813,1814],{"class":1038}," absoluteUrl",[140,1816,1817],{"class":556},"(page",[140,1819,56],{"class":146},[140,1821,1801],{"class":556},[140,1823,56],{"class":146},[140,1825,1826],{"class":556},"image",[140,1828,56],{"class":146},[140,1830,1831],{"class":1038},"replace",[140,1833,1053],{"class":556},[140,1835,1081],{"class":146},[140,1837,1773],{"class":164},[140,1839,1081],{"class":146},[140,1841,1060],{"class":146},[140,1843,382],{"class":146},[140,1845,1777],{"class":164},[140,1847,1081],{"class":146},[140,1849,1850],{"class":556},"))\n",[140,1852,1853,1856],{"class":142,"line":216},[140,1854,1855],{"class":146},"  :",[140,1857,1858],{"class":146}," undefined\n",[31,1860,1861,1862,1865],{},"W efekcie nagłówek artykułu i jego obraz OG to ",[41,1863,1864],{},"ta sama grafika",", tyle że wersja OG nosi dodatkowo logo w rogu.",[26,1867,1869],{"id":1868},"jak-wywołuję-skill-e-gdy-potrzebuję-nowych-grafik","Jak wywołuję SKILL-e, gdy potrzebuję nowych grafik",[31,1871,1872,1873,1876,1877,1007,1880,1883],{},"Cała obsługa sprowadza się do kilku poleceń z poziomu ",[41,1874,1875],{},"Claude Code",". Dla ",[41,1878,1879],{},"tego",[61,1881,1882],{},"order: 5",") wyglądało to tak:",[131,1885,1887],{"className":354,"code":1886,"language":356,"meta":136,"style":136},"# 1. Grafika nagłówkowa artykułu → public\u002Fpics\u002F0005.png\n\u002Fgenerate-article-picture grafiki-artykulow-i-obrazy-og\n\n# 2. Wersja OG artykułu: kopia grafiki + logo w rogu → public\u002Fog\u002F0005.png\n#    (kopiuję pics → og, po czym stempluję logiem)\nnode .claude\u002Fskills\u002Fgenerate-page-og-picture\u002Fstamp-og-logo.mjs public\u002Fog\u002F0005.png\n",[61,1888,1889,1894,1902,1906,1911,1916],{"__ignoreMap":136},[140,1890,1891],{"class":142,"line":143},[140,1892,1893],{"class":1650},"# 1. Grafika nagłówkowa artykułu → public\u002Fpics\u002F0005.png\n",[140,1895,1896,1899],{"class":142,"line":185},[140,1897,1898],{"class":363},"\u002Fgenerate-article-picture",[140,1900,1901],{"class":164}," grafiki-artykulow-i-obrazy-og\n",[140,1903,1904],{"class":142,"line":216},[140,1905,574],{"emptyLinePlaceholder":10},[140,1907,1908],{"class":142,"line":247},[140,1909,1910],{"class":1650},"# 2. Wersja OG artykułu: kopia grafiki + logo w rogu → public\u002Fog\u002F0005.png\n",[140,1912,1913],{"class":142,"line":616},[140,1914,1915],{"class":1650},"#    (kopiuję pics → og, po czym stempluję logiem)\n",[140,1917,1918,1920,1922],{"class":142,"line":624},[140,1919,1750],{"class":363},[140,1921,1753],{"class":164},[140,1923,1756],{"class":164},[31,1925,1926],{},"A dla podstrony — np. strony kontaktowej — jeden SKILL załatwia obraz i aktualizację metadanych:",[131,1928,1930],{"className":354,"code":1929,"language":356,"meta":136,"style":136},"\u002Fgenerate-page-og-picture \u002Fcontact \"Kontakt\"\n",[61,1931,1932],{"__ignoreMap":136},[140,1933,1934,1937,1940,1943,1946],{"class":142,"line":143},[140,1935,1936],{"class":363},"\u002Fgenerate-page-og-picture",[140,1938,1939],{"class":164}," \u002Fcontact",[140,1941,1942],{"class":146}," \"",[140,1944,1945],{"class":164},"Kontakt",[140,1947,1948],{"class":146},"\"\n",[31,1950,1951,1952,1955,1956,1959,1960,1963,1964,494],{},"Pod spodem SKILL nie robi „magii” — po prostu ",[41,1953,1954],{},"uruchamia skrypt pomocniczy"," poleceniem ",[61,1957,1958],{},"node …\u002Fgenerate-page-og-picture.mjs \"\u003Curl>\" \"\u003Ctekst>\"",", podając opcjonalny ",[61,1961,1962],{},"--brief"," ze sceną. Zanim odpali API, warto rzucić okiem na pełny prompt trybem ",[61,1965,1966],{},"--dry-run",[131,1968,1970],{"className":354,"code":1969,"language":356,"meta":136,"style":136},"node .claude\u002Fskills\u002Fgenerate-article-picture\u002Fgenerate-article-picture.mjs \\\n  grafiki-artykulow-i-obrazy-og \\\n  --brief \"a pipeline turning AI artwork into branded social preview cards\" \\\n  --dry-run\n",[61,1971,1972,1982,1989,2003],{"__ignoreMap":136},[140,1973,1974,1976,1979],{"class":142,"line":143},[140,1975,1750],{"class":363},[140,1977,1978],{"class":164}," .claude\u002Fskills\u002Fgenerate-article-picture\u002Fgenerate-article-picture.mjs",[140,1980,1981],{"class":556}," \\\n",[140,1983,1984,1987],{"class":142,"line":185},[140,1985,1986],{"class":164},"  grafiki-artykulow-i-obrazy-og",[140,1988,1981],{"class":556},[140,1990,1991,1994,1996,1999,2001],{"class":142,"line":216},[140,1992,1993],{"class":164},"  --brief",[140,1995,1942],{"class":146},[140,1997,1998],{"class":164},"a pipeline turning AI artwork into branded social preview cards",[140,2000,161],{"class":146},[140,2002,1981],{"class":556},[140,2004,2005],{"class":142,"line":247},[140,2006,2007],{"class":164},"  --dry-run\n",[2009,2010,2011],"note",{},[31,2012,2013,2014,2017,2018,2021],{},"Podział na ",[41,2015,2016],{},"SKILL (decyzja) + skrypt (rygor)"," ma jeszcze jedną zaletę: skrypt można uruchomić ",[41,2019,2020],{},"ręcznie",", bez agenta. SKILL jest wygodną nakładką, ale nie jest niezbędny — co bardzo upraszcza testowanie.",[26,2023,2025],{"id":2024},"wnioski","Wnioski",[31,2027,2028],{},"Trzy obserwacje z tego małego projektu:",[417,2030,2031,2037,2048],{},[420,2032,2033,2036],{},[41,2034,2035],{},"Niedeterminizm AI trzymaj tam, gdzie nie szkodzi."," Model genialnie maluje tło, ale nie zawsze radzi sobie z dokładnym rozmiarem, czcionką i polskimi znakami. Oddanie tła AI, a reszty deterministycznemu skryptowi, daje wynik, który jest jednocześnie ładny i powtarzalny co do piksela.",[420,2038,2039,2042,2043,52,2045,2047],{},[41,2040,2041],{},"Spójność to nie kwestia modelu, lecz stałego promptu."," Tożsamość wizualna serii siedzi w skrypcie (sekcje ",[61,2044,877],{},[61,2046,881],{},"), a nie w zmiennym briefie. Dlatego dziesiąty artykuł będzie pasował do pierwszego, niezależnie od tego, co akurat wymyśli model.",[420,2049,2050,2053,2054,2056],{},[41,2051,2052],{},"Jedno źródło prawdy się opłaca."," Wydzielenie logo do ",[61,2055,1578],{}," sprawia, że marka jest wszędzie identyczna, a jej zmiana to edycja jednego pliku. Ta sama zasada, którą stosuję w kodzie aplikacji, działa równie dobrze przy grafikach.",[31,2058,2059,2060,2063],{},"Najfajniejsze jest jednak to, że ",[41,2061,2062],{},"dodanie grafiki do nowego artykułu to teraz jedno polecenie",". Nie szukam stock-ów, nie walczę z kadrowaniem, nie poprawiam fontów. Piszę treść, odpalam SKILL-a i mam spójną, profesjonalną grafikę — a obraz, który właśnie oglądasz w nagłówku tego artykułu, powstał dokładnie tą drogą.",[2065,2066,2067],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHepR, html code.shiki .sHepR{--shiki-light:#39ADB5;--shiki-light-font-weight:bold;--shiki-default:#89DDFF;--shiki-default-font-weight:bold;--shiki-dark:#89DDFF;--shiki-dark-font-weight:bold}html pre.shiki code .so75L, html code.shiki .so75L{--shiki-light:#E53935;--shiki-light-font-weight:bold;--shiki-default:#F07178;--shiki-default-font-weight:bold;--shiki-dark:#F07178;--shiki-dark-font-weight:bold}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s5tWE, html code.shiki .s5tWE{--shiki-light:#E53935;--shiki-light-font-style:italic;--shiki-default:#F07178;--shiki-default-font-style:italic;--shiki-dark:#F07178;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":136,"searchDepth":185,"depth":185,"links":2069},[2070,2071,2072,2073,2074,2079,2084,2085],{"id":28,"depth":185,"text":29},{"id":101,"depth":185,"text":102},{"id":317,"depth":185,"text":318},{"id":479,"depth":185,"text":480},{"id":538,"depth":185,"text":2075,"children":2076},"SKILL #1: grafiki artykułów — generate-article-picture",[2077,2078],{"id":866,"depth":216,"text":867},{"id":917,"depth":216,"text":918},{"id":1231,"depth":185,"text":2080,"children":2081},"SKILL #2: obrazy OG podstron — generate-page-og-picture",[2082,2083],{"id":1565,"depth":216,"text":1566},{"id":1713,"depth":216,"text":1714},{"id":1868,"depth":185,"text":1869},{"id":2024,"depth":185,"text":2025},"2026-06-07","Dwa SKILL-e Claude Code, które generują grafiki artykułów i obrazy Open Graph stron w powtarzalnym stylu — AI od generowania bazowej grafiki, skrypty od manipulacji obrazem.",false,"md","\u002Fpics\u002F0005.png",{"order":616},"\u002Fblog\u002Fgrafiki-artykulow-i-obrazy-og",{"title":20,"description":2087},{"loc":2092},"blog\u002Fgrafiki-artykulow-i-obrazy-og","H_AqlSBFz9TM6OPcqVs_1Tox0xnIy-vjXvncvQNj-cE",{"id":2098,"title":2099,"author":21,"body":2100,"date":3677,"description":3678,"draft":2088,"extension":2089,"image":3679,"meta":3680,"minRead":6,"navigation":10,"path":37,"seo":3681,"sitemap":3682,"stem":3683,"__hash__":3684},"blog\u002Fblog\u002Fai-driven-development-workflow.md","AI-Driven Development Workflow",{"type":23,"value":2101,"toc":3648},[2102,2104,2126,2135,2152,2156,2169,2193,2199,2203,2214,2236,2245,2248,2591,2598,2602,2622,2634,2638,2651,2658,2665,2668,2731,2734,2742,2746,2757,2831,2849,2856,2871,2874,2883,2919,2926,2939,2945,2954,2957,2964,2977,2986,2990,2993,3038,3045,3125,3136,3143,3160,3169,3179,3182,3189,3195,3204,3215,3222,3236,3263,3266,3275,3298,3305,3312,3321,3324,3353,3364,3368,3375,3384,3398,3402,3412,3417,3473,3478,3484,3502,3515,3517,3520,3556,3583,3587,3593,3638,3645],[26,2103,29],{"id":28},[31,2105,2106,2109,2110,2117,2118,2125],{},[41,2107,2108],{},"AI-Driven Development"," to podejście do wytwarzania oprogramowania, w którym duża część pracy programistycznej — od projektowania, przez implementację, po dokumentację — jest delegowana do agentów AI, a rola człowieka koncentruje się na architekturze, definiowaniu wymagań i weryfikacji wyników. W poprzednich artykułach pokazałem, jak działa to w trybie ",[41,2111,2112],{},[35,2113,2116],{"href":2114,"rel":2115},"https:\u002F\u002Fgithub.com\u002Ffeatures\u002Fcopilot",[111],"GitHub Copilot"," w ",[41,2119,2120],{},[35,2121,2124],{"href":2122,"rel":2123},"https:\u002F\u002Fcode.visualstudio.com\u002F",[111],"VS Code"," — tym razem chcę pójść krok dalej.",[31,2127,2128,2130,2131,2134],{},[41,2129,2099],{}," to dla mnie powtarzalny proces, w którym agenci AI nie są tylko narzędziem do pojedynczych zadań, lecz pełnią rolę wyspecjalizowanych członków zespołu. Każdy z nich odpowiada za konkretny etap cyklu życia projektu: planowanie zadań, ich realizację, aktualizację specyfikacji, dokumentację, ",[285,2132,2133],{},"commit message-e",". Ja pełnię rolę architekta i recenzenta, agenci wykonują pracę zgodnie z wytycznymi.",[31,2136,2137,2138,2144,2145,56],{},"Ten artykuł jest raportem z budowy takiego workflow przy okazji realnego projektu, w którym moim agentem był ",[41,2139,2140],{},[35,2141,1875],{"href":2142,"rel":2143},"https:\u002F\u002Fwww.claude.com\u002Fproduct\u002Fclaude-code",[111]," od ",[41,2146,2147],{},[35,2148,2151],{"href":2149,"rel":2150},"https:\u002F\u002Fwww.anthropic.com\u002F",[111],"Anthropic",[26,2153,2155],{"id":2154},"po-co-kolejna-aplikacja-do-pomiarów","Po co kolejna aplikacja do pomiarów?",[31,2157,2158,2159,64,2162,52,2165,2168],{},"Od dłuższego czasu szukałem aplikacji do regularnej rejestracji ",[41,2160,2161],{},"ciśnienia tętniczego",[41,2163,2164],{},"pulsu",[41,2166,2167],{},"wagi",". Na rynku jest tego sporo, ale każda z aplikacji, na które trafiłem, miała przynajmniej jedną z trzech wad:",[417,2170,2171,2181,2187],{},[420,2172,2173,2176,2177,2180],{},[285,2174,2175],{},"natrętne reklamy"," albo model ",[285,2178,2179],{},"freemium",", w którym kluczowe funkcje są płatne,",[420,2182,2183,2186],{},[285,2184,2185],{},"nadmiar funkcji"," niepotrzebnych mi do mojego celu,",[420,2188,2189,2192],{},[285,2190,2191],{},"brak konkretnej funkcji",", na której mi zależało (np. odczyt wartości ze zdjęcia ekranu ciśnieniomierza).",[31,2194,2195,2196,2198],{},"Stwierdziłem, że to dobra okazja, żeby napisać własną aplikację — niedużą, dostosowaną do moich potrzeb — i jednocześnie przetestować pełny ",[41,2197,2099],{}," na realnym, niekomercyjnym projekcie.",[26,2200,2202],{"id":2201},"specmd-zamiast-openspec","SPEC.md zamiast openspec",[31,2204,2205,2206,2213],{},"Początkowo planowałem oprzeć cały proces o ",[41,2207,2208],{},[35,2209,2212],{"href":2210,"rel":2211},"https:\u002F\u002Fgithub.com\u002FFission-AI\u002FOpenSpec",[111],"openspec"," — narzędzie, które wymusza rygorystyczne podejście do specyfikacji oraz zarządzania zmianami w projekcie. Bardzo szybko stwierdziłem jednak, że dla małej, prywatnej aplikacji jest to przerost formy nad treścią — sporo plików, sporo konwencji, sporo pracy administracyjnej, a ja chciałem skupić się na samym kodzie.",[31,2215,2216,2217,2220,2221,2224,2225,2227,2228,2235],{},"Decyzja była prosta: w pustym folderze tworzę pojedynczy plik ",[41,2218,2219],{},"SPEC.md",", w którym będę utrzymywał specyfikację projektu. Piszę ją „na kolanie”, bez idealnego formatowania, bez kompletu szczegółów — bardziej jak notatkę z głośnego myślenia. Resztę roboty zlecam ",[41,2222,2223],{},"Claude Chat"," (jeszcze nie ",[41,2226,1875],{},", czyli wersji webowej ",[41,2229,2230],{},[35,2231,2234],{"href":2232,"rel":2233},"https:\u002F\u002Fclaude.ai",[111],"claude.ai","):",[131,2237,2239],{"className":547,"code":2238,"language":549,"meta":136,"style":136},"Przed uruchomienie \u002Finit chciałbym mieć dobrej jakości plik SPEC.md projektu. Mam taki napisany \"na kolanie\" i chciałbym, żebyś przekształcił go po względem formy i treści na bardzo profesjonalny. Jeśli potrzebujesz dodatkowych informacji, które powinny się znaleźć w SPEC.md, zadawaj pytania.\n",[61,2240,2241],{"__ignoreMap":136},[140,2242,2243],{"class":142,"line":143},[140,2244,2238],{},[31,2246,2247],{},"i wklejam mój wstępny SPEC.md:",[131,2249,2251],{"className":547,"code":2250,"language":549,"meta":136,"style":136},"Aplikacja web do rejestrowania wyników pomiaru ciśnienia tętniczego krwi, pulsu oraz wagi zalogowanego użytkownika.\n\nStack technologiczny:\n\n- **SQLite** jako baza danych.\n- **Prisma ORM** jako warstwa abstrakcji pomiędzy bazą danych, a _REST API backend_, narzędzie do definiowania schematów tabel oraz zarządzania migracjami w bazie danych.\n- **Nuxt** serwer jako _REST API backend_.\n- **Nuxt** + **Nuxt UI** (wspomagany przez **Tailwind CSS**) jako frontend użytkownika (SSR: false).\n- Moduł **nuxt-auth-utils** wspomagający realizację autentykacji i autoryzacji użytkowników.\n- Biblioteka **zod** do walidacji danych na formularzach frontend oraz dannych przekazywanych do endpoint-ów REST API.\n- Biblioteki **unovis** do prezentacji danych w formie wykresów i grafów.\n\nWzorce projektowe:\n\n- **Język interfejsu i komunikatów**: polski\n- **Walidacja request body** — Zod schema w endpoincie API (`safeParse`)\n- **Komunikaty błędów** — `useAppMessages()` store → `AppMessages.vue` → `useToast()`\n- **Endpoint-y chronione** — `requireUserSession(event)`\n- **Store pattern** — Pinia setup stores (`defineStore` z composition API)\n- **Okna dialogowe (Modal\u002FSlideover)** — sterowane przez `useOverlay` (nie `v-model:open`). Dialogi potwierdzenia przez composable `useConfirmDialog` oparty na `useOverlay`\n- **If bez klamer**: jeśli po `if` występuje tylko jedno polecenie, nie używaj nawiasów `{ }` (np. `if (!x) return`)\n- **Handler-y API (server\u002Fapi\u002F)**: minimum kodu — tylko kontrola autentykacji\u002Fautoryzacji (`requireUserSession`, `checkUserHasAnyRole`) i walidacja wejścia (Zod). Cała logika biznesowa i dostęp do bazy danych należą do serwisów w `server\u002Fservices\u002F`\n\nInterfejs użytkownika:\n\n- Prosty, w układzie pionowym dla urządzeń mobilnych.\n- Wspólnych layout dla wszystkich ekranów: top nav bar z guzikiem powrotu na stronę domową, nazwa aplikacji CPW oraz hamburger menu (Mój profil, Wyloguj) oraz główna przestrzeń aplikacji.\n- Strona domowa to 3 duże guziki: Ciśnienie \u002F puls, Waga, Statystyki.\n- Strony dodatkowe:\n  - logowania uzytkownika\n  - mój profil, na której można zmienić sobie hasło.\n\nPlanowanie funkcjonalności:\n\n- Rejestrowanie wagi - po wybraniu Waga z ekranu głównego -> strona z formularzem:\n  - Pole na wagę\n  - Pole na date wykonania pomiaru, domyślnie _now_ z możliwością ręcznego ustawienia daty i czasu\n- Rejestrowanie ciśnienie i pulsu - po wybraniu Ciśnienie \u002F puls z ekranu głównego -> strona z formularzem:\n  - Pole na ciśnienie skurczowe\n  - Pole na ciśnienie rozkurczowe\n  - Pole na puls\n  - Pole na date wykonania pomiaru, domyślnie _now_ z możliwością ręcznego ustawienia daty i czasu\n  - Możliwość przekazania wartości dla pierwszych trzech pól poprzez flow:\n    - aktywacja aparatu fotograficznego z podglądem co widzi obiektyw\n    - użytkownik robi zdjęcia ekranu ciśnieniomierza, na którym widać wartości zmierzone\n    - zdjęcie przekazane na serwer, który następnie wysyła je do analizy AI, np. z pośrednictwem OpenRouter\n    - AI zwraca w formacie JSON 3 wyłuskane ze zdjęcia wartości\n- Statystyki - po wybraniu Statystyki ekranu głównego -> strona z formularzem:\n  - Formularz ustalania zakresu analizowanych danych: 7 dni, 2 tygodnie, miesiąc, kwartał, rok.\n  - Wykresy:\n    - Ciśnienie - dwie linie: skurczowe i rozkurczowe\n    - Puls\n    - Waga\n\nAutentykacja:\n\n- Oprócz strony \u002Flogin i endpoint \u002Fapi\u002Flogin, wszystkie pozostały strony i endpoint-y wymagają aktywnej sesji zalogowanego użytkownika. Jej brak kieruje na stronę \u002Flogin.\n- Aplikacja nie daje możliwości rejestracji nowego użytkownika. Administrator systemu ma do dyspozycji specjalnych skrypt _typescript_, którym może dodać nowego do aplikacji.\n\nAutoryzacja:\n\n- Użytkownik ma dostęp tylko i wyłączenie do pomiarów zarejestrowanych przez siebie.\n",[61,2252,2253,2258,2262,2267,2271,2276,2281,2286,2291,2296,2301,2306,2310,2315,2319,2324,2329,2334,2339,2344,2349,2354,2360,2365,2371,2376,2382,2388,2394,2400,2406,2412,2417,2423,2428,2434,2440,2446,2452,2458,2464,2470,2475,2481,2487,2493,2499,2505,2511,2517,2523,2529,2535,2541,2546,2552,2557,2563,2569,2574,2580,2585],{"__ignoreMap":136},[140,2254,2255],{"class":142,"line":143},[140,2256,2257],{},"Aplikacja web do rejestrowania wyników pomiaru ciśnienia tętniczego krwi, pulsu oraz wagi zalogowanego użytkownika.\n",[140,2259,2260],{"class":142,"line":185},[140,2261,574],{"emptyLinePlaceholder":10},[140,2263,2264],{"class":142,"line":216},[140,2265,2266],{},"Stack technologiczny:\n",[140,2268,2269],{"class":142,"line":247},[140,2270,574],{"emptyLinePlaceholder":10},[140,2272,2273],{"class":142,"line":616},[140,2274,2275],{},"- **SQLite** jako baza danych.\n",[140,2277,2278],{"class":142,"line":624},[140,2279,2280],{},"- **Prisma ORM** jako warstwa abstrakcji pomiędzy bazą danych, a _REST API backend_, narzędzie do definiowania schematów tabel oraz zarządzania migracjami w bazie danych.\n",[140,2282,2283],{"class":142,"line":641},[140,2284,2285],{},"- **Nuxt** serwer jako _REST API backend_.\n",[140,2287,2288],{"class":142,"line":649},[140,2289,2290],{},"- **Nuxt** + **Nuxt UI** (wspomagany przez **Tailwind CSS**) jako frontend użytkownika (SSR: false).\n",[140,2292,2293],{"class":142,"line":657},[140,2294,2295],{},"- Moduł **nuxt-auth-utils** wspomagający realizację autentykacji i autoryzacji użytkowników.\n",[140,2297,2298],{"class":142,"line":665},[140,2299,2300],{},"- Biblioteka **zod** do walidacji danych na formularzach frontend oraz dannych przekazywanych do endpoint-ów REST API.\n",[140,2302,2303],{"class":142,"line":704},[140,2304,2305],{},"- Biblioteki **unovis** do prezentacji danych w formie wykresów i grafów.\n",[140,2307,2308],{"class":142,"line":730},[140,2309,574],{"emptyLinePlaceholder":10},[140,2311,2312],{"class":142,"line":738},[140,2313,2314],{},"Wzorce projektowe:\n",[140,2316,2317],{"class":142,"line":756},[140,2318,574],{"emptyLinePlaceholder":10},[140,2320,2321],{"class":142,"line":773},[140,2322,2323],{},"- **Język interfejsu i komunikatów**: polski\n",[140,2325,2326],{"class":142,"line":790},[140,2327,2328],{},"- **Walidacja request body** — Zod schema w endpoincie API (`safeParse`)\n",[140,2330,2331],{"class":142,"line":1409},[140,2332,2333],{},"- **Komunikaty błędów** — `useAppMessages()` store → `AppMessages.vue` → `useToast()`\n",[140,2335,2336],{"class":142,"line":1424},[140,2337,2338],{},"- **Endpoint-y chronione** — `requireUserSession(event)`\n",[140,2340,2341],{"class":142,"line":1440},[140,2342,2343],{},"- **Store pattern** — Pinia setup stores (`defineStore` z composition API)\n",[140,2345,2346],{"class":142,"line":1455},[140,2347,2348],{},"- **Okna dialogowe (Modal\u002FSlideover)** — sterowane przez `useOverlay` (nie `v-model:open`). Dialogi potwierdzenia przez composable `useConfirmDialog` oparty na `useOverlay`\n",[140,2350,2351],{"class":142,"line":1510},[140,2352,2353],{},"- **If bez klamer**: jeśli po `if` występuje tylko jedno polecenie, nie używaj nawiasów `{ }` (np. `if (!x) return`)\n",[140,2355,2357],{"class":142,"line":2356},22,[140,2358,2359],{},"- **Handler-y API (server\u002Fapi\u002F)**: minimum kodu — tylko kontrola autentykacji\u002Fautoryzacji (`requireUserSession`, `checkUserHasAnyRole`) i walidacja wejścia (Zod). Cała logika biznesowa i dostęp do bazy danych należą do serwisów w `server\u002Fservices\u002F`\n",[140,2361,2363],{"class":142,"line":2362},23,[140,2364,574],{"emptyLinePlaceholder":10},[140,2366,2368],{"class":142,"line":2367},24,[140,2369,2370],{},"Interfejs użytkownika:\n",[140,2372,2374],{"class":142,"line":2373},25,[140,2375,574],{"emptyLinePlaceholder":10},[140,2377,2379],{"class":142,"line":2378},26,[140,2380,2381],{},"- Prosty, w układzie pionowym dla urządzeń mobilnych.\n",[140,2383,2385],{"class":142,"line":2384},27,[140,2386,2387],{},"- Wspólnych layout dla wszystkich ekranów: top nav bar z guzikiem powrotu na stronę domową, nazwa aplikacji CPW oraz hamburger menu (Mój profil, Wyloguj) oraz główna przestrzeń aplikacji.\n",[140,2389,2391],{"class":142,"line":2390},28,[140,2392,2393],{},"- Strona domowa to 3 duże guziki: Ciśnienie \u002F puls, Waga, Statystyki.\n",[140,2395,2397],{"class":142,"line":2396},29,[140,2398,2399],{},"- Strony dodatkowe:\n",[140,2401,2403],{"class":142,"line":2402},30,[140,2404,2405],{},"  - logowania uzytkownika\n",[140,2407,2409],{"class":142,"line":2408},31,[140,2410,2411],{},"  - mój profil, na której można zmienić sobie hasło.\n",[140,2413,2415],{"class":142,"line":2414},32,[140,2416,574],{"emptyLinePlaceholder":10},[140,2418,2420],{"class":142,"line":2419},33,[140,2421,2422],{},"Planowanie funkcjonalności:\n",[140,2424,2426],{"class":142,"line":2425},34,[140,2427,574],{"emptyLinePlaceholder":10},[140,2429,2431],{"class":142,"line":2430},35,[140,2432,2433],{},"- Rejestrowanie wagi - po wybraniu Waga z ekranu głównego -> strona z formularzem:\n",[140,2435,2437],{"class":142,"line":2436},36,[140,2438,2439],{},"  - Pole na wagę\n",[140,2441,2443],{"class":142,"line":2442},37,[140,2444,2445],{},"  - Pole na date wykonania pomiaru, domyślnie _now_ z możliwością ręcznego ustawienia daty i czasu\n",[140,2447,2449],{"class":142,"line":2448},38,[140,2450,2451],{},"- Rejestrowanie ciśnienie i pulsu - po wybraniu Ciśnienie \u002F puls z ekranu głównego -> strona z formularzem:\n",[140,2453,2455],{"class":142,"line":2454},39,[140,2456,2457],{},"  - Pole na ciśnienie skurczowe\n",[140,2459,2461],{"class":142,"line":2460},40,[140,2462,2463],{},"  - Pole na ciśnienie rozkurczowe\n",[140,2465,2467],{"class":142,"line":2466},41,[140,2468,2469],{},"  - Pole na puls\n",[140,2471,2473],{"class":142,"line":2472},42,[140,2474,2445],{},[140,2476,2478],{"class":142,"line":2477},43,[140,2479,2480],{},"  - Możliwość przekazania wartości dla pierwszych trzech pól poprzez flow:\n",[140,2482,2484],{"class":142,"line":2483},44,[140,2485,2486],{},"    - aktywacja aparatu fotograficznego z podglądem co widzi obiektyw\n",[140,2488,2490],{"class":142,"line":2489},45,[140,2491,2492],{},"    - użytkownik robi zdjęcia ekranu ciśnieniomierza, na którym widać wartości zmierzone\n",[140,2494,2496],{"class":142,"line":2495},46,[140,2497,2498],{},"    - zdjęcie przekazane na serwer, który następnie wysyła je do analizy AI, np. z pośrednictwem OpenRouter\n",[140,2500,2502],{"class":142,"line":2501},47,[140,2503,2504],{},"    - AI zwraca w formacie JSON 3 wyłuskane ze zdjęcia wartości\n",[140,2506,2508],{"class":142,"line":2507},48,[140,2509,2510],{},"- Statystyki - po wybraniu Statystyki ekranu głównego -> strona z formularzem:\n",[140,2512,2514],{"class":142,"line":2513},49,[140,2515,2516],{},"  - Formularz ustalania zakresu analizowanych danych: 7 dni, 2 tygodnie, miesiąc, kwartał, rok.\n",[140,2518,2520],{"class":142,"line":2519},50,[140,2521,2522],{},"  - Wykresy:\n",[140,2524,2526],{"class":142,"line":2525},51,[140,2527,2528],{},"    - Ciśnienie - dwie linie: skurczowe i rozkurczowe\n",[140,2530,2532],{"class":142,"line":2531},52,[140,2533,2534],{},"    - Puls\n",[140,2536,2538],{"class":142,"line":2537},53,[140,2539,2540],{},"    - Waga\n",[140,2542,2544],{"class":142,"line":2543},54,[140,2545,574],{"emptyLinePlaceholder":10},[140,2547,2549],{"class":142,"line":2548},55,[140,2550,2551],{},"Autentykacja:\n",[140,2553,2555],{"class":142,"line":2554},56,[140,2556,574],{"emptyLinePlaceholder":10},[140,2558,2560],{"class":142,"line":2559},57,[140,2561,2562],{},"- Oprócz strony \u002Flogin i endpoint \u002Fapi\u002Flogin, wszystkie pozostały strony i endpoint-y wymagają aktywnej sesji zalogowanego użytkownika. Jej brak kieruje na stronę \u002Flogin.\n",[140,2564,2566],{"class":142,"line":2565},58,[140,2567,2568],{},"- Aplikacja nie daje możliwości rejestracji nowego użytkownika. Administrator systemu ma do dyspozycji specjalnych skrypt _typescript_, którym może dodać nowego do aplikacji.\n",[140,2570,2572],{"class":142,"line":2571},59,[140,2573,574],{"emptyLinePlaceholder":10},[140,2575,2577],{"class":142,"line":2576},60,[140,2578,2579],{},"Autoryzacja:\n",[140,2581,2583],{"class":142,"line":2582},61,[140,2584,574],{"emptyLinePlaceholder":10},[140,2586,2588],{"class":142,"line":2587},62,[140,2589,2590],{},"- Użytkownik ma dostęp tylko i wyłączenie do pomiarów zarejestrowanych przez siebie.\n",[31,2592,2593,2594,2597],{},"Chat zadał mi ",[41,2595,2596],{},"11 pytań"," doprecyzowujących (m.in. o politykę haseł, format dat, jednostki, obsługę stref czasowych, sposób przechowywania zdjęć), a następnie przygotował naprawdę porządną specyfikację — uporządkowaną logicznie, z rozdziałami, jednolitym formatowaniem i kompletem informacji, na których mógł oprzeć się dalszy proces.",[26,2599,2601],{"id":2600},"claude-code-init-i-claudemd","Claude Code, \u002Finit i CLAUDE.md",[31,2603,2604,2605,2607,2608,2610,2611,2614,2615,2617,2618,2621],{},"Mając gotowy plik ",[41,2606,2219],{},", do gry wchodzi ",[41,2609,1875],{}," — wersja agenta uruchamiana lokalnie z konsoli, w katalogu projektu. Pierwsze polecenie to wbudowany ",[61,2612,2613],{},"\u002Finit",", który analizuje zawartość katalogu (a w moim przypadku — istniejący już ",[41,2616,2219],{},") i generuje plik ",[41,2619,2620],{},"CLAUDE.md"," z instrukcjami dla samego siebie.",[31,2623,2624,2626,2627,2629,2630,2633],{},[41,2625,2620],{}," jest tym, co odróżnia ",[41,2628,1875],{}," od „zwykłego” asystenta — plik ten automatycznie ląduje w kontekście ",[41,2631,2632],{},"każdej"," sesji w tym katalogu. To jest projektowa pamięć agenta: wzorce, konwencje, polecenia npm, struktura folderów, ograniczenia. Każde nowe uruchomienie zaczyna się od tej samej, spójnej wiedzy o projekcie.",[26,2635,2637],{"id":2636},"skill-e-claude-code","SKILL-e Claude Code",[31,2639,2640,2641,2643,2644,2647,2648,56],{},"Drugim mechanizmem, który zdecydował o całym workflow, są ",[41,2642,2637],{}," — wyspecjalizowane „role” agenta, każda z własnym opisem, parametrami i instrukcjami postępowania. Tworzy się je jako pliki w katalogu ",[61,2645,2646],{},".claude\u002Fskills\u002F\u003Cnazwa-skilla>\u002FSKILL.md"," i wywołuje z poziomu CLI poprzez polecenia ",[61,2649,2650],{},"\u002F\u003Cnazwa-skilla>",[31,2652,2653,2654,2657],{},"Samodzielne pisanie dobrych SKILL-i jest trudne — wymaga znajomości konwencji i sporej dyscypliny. Sztuka jest jednak prostsza, niż się wydaje: opisuję agentowi co chcę osiągnąć i to ",[41,2655,2656],{},"on"," przygotowuje mi gotowego SKILL-a. Każdy kolejny w tym artykule powstał właśnie w ten sposób.",[26,2659,2661,2662],{"id":2660},"skill-1-planista-zadań-update-tasks-list","SKILL #1: planista zadań — ",[61,2663,2664],{},"update-tasks-list",[31,2666,2667],{},"Pierwsza decyzja dotycząca workflow: potrzebuję wyspecjalizowanego planisty, który na podstawie specyfikacji rozpisze listę zadań do realizacji. Proszę agenta o przygotowanie pierwszego SKILL-a:",[131,2669,2671],{"className":547,"code":2670,"language":549,"meta":136,"style":136},"Przygotuj SKILL-a o nazwie **update-tasks-list**, który przejrzy specyfikację w `.\u002FSPEC.md` i `.\u002FCLAUDE.md` i w pliku `.\u002FTASKS.md` dopisze zadania, które nie były jeszcze realizowane (wynikają z nowo dodanej specyfikacji).\nLista zadań to tabela MD z kolumnami:\n\n- Numer zadania w formacie NNNN, np. 0001, 0204, w formie linku do pliku `NNNN.md`\n- Status - wartości Planowane, Zrealizowane\n- Tytuł - ogólny, skrócony opis zadania\n\nSKILL musi logicznie układać zadania, czyli zawsze na początku musi być praca w bazie danych, modelach danych, potem REST API, a na końcu frontend.\nJedno zadanie może być przekrojowe przez warstwy aplikacji i raczej nie rozbijamy się na bardzo małe zadania, tylko zadania realizujące konkretne funkcjonalności aplikacji, np: zarządzanie użytkownikami, które będzie realizowało utworzenie tabeli, modeli danych, DTOs, endpoint-ów.\nLogiczny plan realizacji każdego zadania musi znaleźć się osobnym pliku o nazwie `NNNN.md` w folderze `.\u002F_TASKS\u002F`.\nJeśli SKILL pierwszy raz uruchomiony, musi utworzyć plik `.\u002FTASKS.md` oraz folder `.\u002F_TASKS\u002F` z wszystkimi zadaniami, które wymagają realizacji w ramach specyfikacji.\nPrzy kolejnych uruchomieniach, dodaje nowe zadania, które wynikają z dodanej specyfikacji.\n",[61,2672,2673,2678,2683,2687,2692,2697,2702,2706,2711,2716,2721,2726],{"__ignoreMap":136},[140,2674,2675],{"class":142,"line":143},[140,2676,2677],{},"Przygotuj SKILL-a o nazwie **update-tasks-list**, który przejrzy specyfikację w `.\u002FSPEC.md` i `.\u002FCLAUDE.md` i w pliku `.\u002FTASKS.md` dopisze zadania, które nie były jeszcze realizowane (wynikają z nowo dodanej specyfikacji).\n",[140,2679,2680],{"class":142,"line":185},[140,2681,2682],{},"Lista zadań to tabela MD z kolumnami:\n",[140,2684,2685],{"class":142,"line":216},[140,2686,574],{"emptyLinePlaceholder":10},[140,2688,2689],{"class":142,"line":247},[140,2690,2691],{},"- Numer zadania w formacie NNNN, np. 0001, 0204, w formie linku do pliku `NNNN.md`\n",[140,2693,2694],{"class":142,"line":616},[140,2695,2696],{},"- Status - wartości Planowane, Zrealizowane\n",[140,2698,2699],{"class":142,"line":624},[140,2700,2701],{},"- Tytuł - ogólny, skrócony opis zadania\n",[140,2703,2704],{"class":142,"line":641},[140,2705,574],{"emptyLinePlaceholder":10},[140,2707,2708],{"class":142,"line":649},[140,2709,2710],{},"SKILL musi logicznie układać zadania, czyli zawsze na początku musi być praca w bazie danych, modelach danych, potem REST API, a na końcu frontend.\n",[140,2712,2713],{"class":142,"line":657},[140,2714,2715],{},"Jedno zadanie może być przekrojowe przez warstwy aplikacji i raczej nie rozbijamy się na bardzo małe zadania, tylko zadania realizujące konkretne funkcjonalności aplikacji, np: zarządzanie użytkownikami, które będzie realizowało utworzenie tabeli, modeli danych, DTOs, endpoint-ów.\n",[140,2717,2718],{"class":142,"line":665},[140,2719,2720],{},"Logiczny plan realizacji każdego zadania musi znaleźć się osobnym pliku o nazwie `NNNN.md` w folderze `.\u002F_TASKS\u002F`.\n",[140,2722,2723],{"class":142,"line":704},[140,2724,2725],{},"Jeśli SKILL pierwszy raz uruchomiony, musi utworzyć plik `.\u002FTASKS.md` oraz folder `.\u002F_TASKS\u002F` z wszystkimi zadaniami, które wymagają realizacji w ramach specyfikacji.\n",[140,2727,2728],{"class":142,"line":730},[140,2729,2730],{},"Przy kolejnych uruchomieniach, dodaje nowe zadania, które wynikają z dodanej specyfikacji.\n",[31,2732,2733],{},"Agent przygotował kompletny plik SKILL-a wraz z opisem, parametrami i procedurą.",[2009,2735,2736],{},[31,2737,2738,2739,56],{},"Na końcu artykułu udostępniam link do pobrania wszystkich SKILL-i użytych w tym projekcie — ",[41,2740,2741],{},"bezpłatnie!",[26,2743,2745],{"id":2744},"pierwsze-uruchomienie-planisty","Pierwsze uruchomienie planisty",[31,2747,2748,2749,2752,2753,2756],{},"Wywołuję ",[61,2750,2751],{},"\u002Fupdate-tasks-list"," i otrzymuję plik ",[41,2754,2755],{},"TASKS.md"," z listą zadań ułożonych logicznie od bazy danych, przez API, po frontend:",[131,2758,2760],{"className":547,"code":2759,"language":549,"meta":136,"style":136},"# Zadania projektu CPW\n\n| Nr                       | Status       | Tytuł                                               |\n| ------------------------ | ------------ | --------------------------------------------------- |\n| [0001](.\u002F_TASKS\u002F0001.md) | Zrealizowane | Inicjalizacja projektu i konfiguracja               |\n| [0002](.\u002F_TASKS\u002F0002.md) | Zrealizowane | Model danych i migracje bazy danych                 |\n| [0003](.\u002F_TASKS\u002F0003.md) | Zrealizowane | Skrypt CLI dodawania użytkowników                   |\n| [0004](.\u002F_TASKS\u002F0004.md) | Zrealizowane | Autentykacja i autoryzacja                          |\n| [0005](.\u002F_TASKS\u002F0005.md) | Zrealizowane | Wspólny layout, nawigacja i infrastruktura frontend |\n| [0006](.\u002F_TASKS\u002F0006.md) | Zrealizowane | Pomiary ciśnienia i pulsu                           |\n| [0007](.\u002F_TASKS\u002F0007.md) | Zrealizowane | Pomiary wagi                                        |\n| [0008](.\u002F_TASKS\u002F0008.md) | Zrealizowane | Statystyki i wykresy                                |\n| [0009](.\u002F_TASKS\u002F0009.md) | Zrealizowane | Profil użytkownika — zmiana hasła                   |\n| [0010](.\u002F_TASKS\u002F0010.md) | Zrealizowane | Analiza zdjęcia ciśnieniomierza przez AI            |\n",[61,2761,2762,2767,2771,2776,2781,2786,2791,2796,2801,2806,2811,2816,2821,2826],{"__ignoreMap":136},[140,2763,2764],{"class":142,"line":143},[140,2765,2766],{},"# Zadania projektu CPW\n",[140,2768,2769],{"class":142,"line":185},[140,2770,574],{"emptyLinePlaceholder":10},[140,2772,2773],{"class":142,"line":216},[140,2774,2775],{},"| Nr                       | Status       | Tytuł                                               |\n",[140,2777,2778],{"class":142,"line":247},[140,2779,2780],{},"| ------------------------ | ------------ | --------------------------------------------------- |\n",[140,2782,2783],{"class":142,"line":616},[140,2784,2785],{},"| [0001](.\u002F_TASKS\u002F0001.md) | Zrealizowane | Inicjalizacja projektu i konfiguracja               |\n",[140,2787,2788],{"class":142,"line":624},[140,2789,2790],{},"| [0002](.\u002F_TASKS\u002F0002.md) | Zrealizowane | Model danych i migracje bazy danych                 |\n",[140,2792,2793],{"class":142,"line":641},[140,2794,2795],{},"| [0003](.\u002F_TASKS\u002F0003.md) | Zrealizowane | Skrypt CLI dodawania użytkowników                   |\n",[140,2797,2798],{"class":142,"line":649},[140,2799,2800],{},"| [0004](.\u002F_TASKS\u002F0004.md) | Zrealizowane | Autentykacja i autoryzacja                          |\n",[140,2802,2803],{"class":142,"line":657},[140,2804,2805],{},"| [0005](.\u002F_TASKS\u002F0005.md) | Zrealizowane | Wspólny layout, nawigacja i infrastruktura frontend |\n",[140,2807,2808],{"class":142,"line":665},[140,2809,2810],{},"| [0006](.\u002F_TASKS\u002F0006.md) | Zrealizowane | Pomiary ciśnienia i pulsu                           |\n",[140,2812,2813],{"class":142,"line":704},[140,2814,2815],{},"| [0007](.\u002F_TASKS\u002F0007.md) | Zrealizowane | Pomiary wagi                                        |\n",[140,2817,2818],{"class":142,"line":730},[140,2819,2820],{},"| [0008](.\u002F_TASKS\u002F0008.md) | Zrealizowane | Statystyki i wykresy                                |\n",[140,2822,2823],{"class":142,"line":738},[140,2824,2825],{},"| [0009](.\u002F_TASKS\u002F0009.md) | Zrealizowane | Profil użytkownika — zmiana hasła                   |\n",[140,2827,2828],{"class":142,"line":756},[140,2829,2830],{},"| [0010](.\u002F_TASKS\u002F0010.md) | Zrealizowane | Analiza zdjęcia ciśnieniomierza przez AI            |\n",[31,2832,2833,2834,2837,2838,2841,2842,2845,2846,56],{},"Równocześnie powstał folder ",[61,2835,2836],{},"_TASKS\u002F"," z dziesięcioma plikami ",[61,2839,2840],{},"NNNN.md",", w których agent rozpisał szczegółowe kroki realizacji każdego z zadań — od migracji ",[41,2843,2844],{},"Prisma",", przez DTO, po komponenty ",[41,2847,2848],{},"Vue",[26,2850,2852,2853],{"id":2851},"skill-2-wykonawca-do-task","SKILL #2: wykonawca — ",[61,2854,2855],{},"do-task",[31,2857,2858,2859,2862,2863,2866,2867,2870],{},"Mogłem teraz puścić agenta luzem i poprosić go o realizację ",[41,2860,2861],{},"wszystkich"," zadań naraz. Wolę jednak inne podejście: zadanie po zadaniu, weryfikacja przez ",[61,2864,2865],{},"git diff",", świadomy ",[61,2868,2869],{},"git commit",", dopiero potem kolejne. Po pierwsze — zachowuję kontrolę nad jakością. Po drugie — historia commit-ów ma sens i da się ją po latach czytać.",[31,2872,2873],{},"Zlecam agentowi przygotowanie kolejnego SKILL-a:",[131,2875,2877],{"className":547,"code":2876,"language":549,"meta":136,"style":136},"Przygotuj SKILL-a o nazwie **do-task**, który zrealizuje zadania o podany numerze. Numer podajemy jako parametr wywołania SKILL-a. Po zakończeniu realizacji zadania zmiana status zadania w pliku `.\u002FTASKS.md`.\n",[61,2878,2879],{"__ignoreMap":136},[140,2880,2881],{"class":142,"line":143},[140,2882,2876],{},[31,2884,2885,2886,2889,2890,2893,2894,64,2897,64,2900,64,2903,2906,2907,52,2909,2911,2912,2915,2916,2918],{},"Uruchamiam pierwsze prace poleceniem ",[61,2887,2888],{},"\u002Fdo-task 1"," i czekam, aż agent przygotuje strukturę projektu. Po kilku minutach mam zainicjowany projekt ",[41,2891,2892],{},"Nuxt"," ze skonfigurowanym ",[41,2895,2896],{},"TypeScript",[41,2898,2899],{},"ESLint",[41,2901,2902],{},"Prettier",[41,2904,2905],{},"Vitest",". Po ",[61,2908,2865],{},[61,2910,2869],{}," przechodzę do ",[61,2913,2914],{},"\u002Fdo-task 2"," — modele bazy danych, schemat ",[41,2917,2844],{},", pierwsza migracja. I tak dalej.",[26,2920,2922,2923],{"id":2921},"skill-3-korygujący-update-task","SKILL #3: korygujący — ",[61,2924,2925],{},"update-task",[31,2927,2928,2929,2932,2933,2935,2936,56],{},"Po pierwszych implementacjach szybko okazało się, że niezależnie od jakości wyjściowej, zawsze coś trzeba poprawić — kosmetycznie, koncepcyjnie lub funkcjonalnie. Można poprawki zlecać agentowi w tej samej sesji (dopóki nie wyczyścimy kontekstu poleceniem ",[61,2930,2931],{},"\u002Fclear","), ale wtedy ślad po decyzji znika — plik ",[61,2934,2840],{}," z opisem zadania pozostaje nieaktualny, a zmiany są tylko w kodzie i ",[61,2937,2938],{},"git log",[31,2940,2941,2942,2944],{},"Potrzebny był osobny SKILL, który zaktualizuje plik zadania w folderze ",[61,2943,2836],{},", a następnie od razu przeprowadzi zmianę w kodzie:",[131,2946,2948],{"className":547,"code":2947,"language":549,"meta":136,"style":136},"Przygotuj SKILL-a o nazwie **update-task**, który przyjmuje dwa parametry: nr zadania oraz nowe wymagania związane z zadaniem (nowa funkcja, rozbudowa, poprawka). SKILL aktualizuje plik `NNNN.md` w folderze `.\u002F_TASKS\u002F` zgodnie z podanymi oczekiwaniami i od razu przystępuje do realizacji w kodzie, jeśli jest taka potrzeba.\n",[61,2949,2950],{"__ignoreMap":136},[140,2951,2952],{"class":142,"line":143},[140,2953,2947],{},[31,2955,2956],{},"Od tego momentu każda poprawka jest udokumentowana w pliku zadania — opis aktualnego stanu wymagania i opis zmiany żyją razem z kodem.",[26,2958,2960,2961],{"id":2959},"skill-4-aktualizacja-specyfikacji-update-spec","SKILL #4: aktualizacja specyfikacji — ",[61,2962,2963],{},"update-spec",[31,2965,2966,2967,2970,2971,2973,2974,2976],{},"Po realizacji pierwszych dziesięciu zadań, w trakcie codziennego korzystania z aplikacji, zaczęły pojawiać się pomysły na nowe funkcje i usprawnienia. Każdy taki pomysł powinien najpierw trafić do ",[41,2968,2969],{},"specyfikacji"," (czyli plików ",[41,2972,2219],{}," i ewentualnie ",[41,2975,2620],{},"), a dopiero potem wpływać na listę zadań. Robienie tego ręcznie mijałoby się z celem całego workflow — zlecam agentowi czwartego SKILL-a:",[131,2978,2980],{"className":547,"code":2979,"language":549,"meta":136,"style":136},"Przygotuj SKILL-a o nazwie **update-spec**, który przyjmuje jeden parametr - opis planowanej zmiany koncepcyjnej, nowej funkcjonalności, nowego procesu, który ma być obsługiwany przez aplikację. SKILL aktualizuje plik `.\u002FSPEC.md` i jeśli jest taka potrzeba pliki `.\u002FCLAUDE.md`.\n",[61,2981,2982],{"__ignoreMap":136},[140,2983,2984],{"class":142,"line":143},[140,2985,2979],{},[26,2987,2989],{"id":2988},"pętla-rozwojowa","Pętla rozwojowa",[31,2991,2992],{},"W tym momencie mam już komplet ról potrzebnych do utrzymania projektu i wpadam w wyraźnie powtarzalną pętlę:",[417,2994,2995,3004,3011,3019,3027],{},[420,2996,2997,3000,3001],{},[285,2998,2999],{},"nowy pomysł \u002F funkcja"," → aktualizacja specyfikacji: ",[61,3002,3003],{},"\u002Fupdate-spec",[420,3005,3006,1774,3009],{},[285,3007,3008],{},"nowe zadania wynikające ze specyfikacji",[61,3010,2751],{},[420,3012,3013,1774,3016],{},[285,3014,3015],{},"realizacja zadań",[61,3017,3018],{},"\u002Fdo-task \u003Cnr>",[420,3020,3021,1774,3024],{},[285,3022,3023],{},"ewentualne poprawki",[61,3025,3026],{},"\u002Fupdate-task \u003Cnr> \u003Copis zmiany>",[420,3028,3029,1774,3032,3034,3035],{},[285,3030,3031],{},"zatwierdzenie",[61,3033,2869],{}," + ",[61,3036,3037],{},"git push",[31,3039,3040,3041,3044],{},"W ten sposób dołożyłem do projektu dodatkowych ",[41,3042,3043],{},"13 zadań",", które przeszły przez tę samą ścieżkę co pierwsza dziesiątka:",[131,3046,3048],{"className":547,"code":3047,"language":549,"meta":136,"style":136},"| Nr                       | Status       | Tytuł                                                 |\n| ------------------------ | ------------ | ----------------------------------------------------- |\n| [0011](.\u002F_TASKS\u002F0011.md) | Zrealizowane | Ikony aplikacji i metadane web app                    |\n| [0012](.\u002F_TASKS\u002F0012.md) | Zrealizowane | Resetowanie stanu Pinia store'ów przy wylogowaniu     |\n| [0013](.\u002F_TASKS\u002F0013.md) | Zrealizowane | Konfigurowalna długość sesji (NUXT_SESSION_VALIDITY)  |\n| [0014](.\u002F_TASKS\u002F0014.md) | Zrealizowane | Refaktor walidacji wejścia API (DTOs + helper-y)      |\n| [0015](.\u002F_TASKS\u002F0015.md) | Zrealizowane | Warstwa bezpieczeństwa (nuxt-security)                |\n| [0016](.\u002F_TASKS\u002F0016.md) | Zrealizowane | Centralna funkcja apiCall dla wywołań API frontend-u  |\n| [0017](.\u002F_TASKS\u002F0017.md) | Zrealizowane | Globalny wskaźnik ładowania (appStatus + AppLoader)   |\n| [0018](.\u002F_TASKS\u002F0018.md) | Zrealizowane | Refaktor systemu komunikatów (appStatus + AppToaster) |\n| [0019](.\u002F_TASKS\u002F0019.md) | Zrealizowane | Testy integracyjne REST API                           |\n| [0020](.\u002F_TASKS\u002F0020.md) | Zrealizowane | Profil — data urodzenia i wzrost                      |\n| [0021](.\u002F_TASKS\u002F0021.md) | Zrealizowane | Statystyki — wskaźnik BMI w sekcji Waga               |\n| [0022](.\u002F_TASKS\u002F0022.md) | Zrealizowane | Konfiguracja SMTP i serwis wysyłki e-mail             |\n| [0023](.\u002F_TASKS\u002F0023.md) | Zrealizowane | Agent przypomnień e-mail                              |\n",[61,3049,3050,3055,3060,3065,3070,3075,3080,3085,3090,3095,3100,3105,3110,3115,3120],{"__ignoreMap":136},[140,3051,3052],{"class":142,"line":143},[140,3053,3054],{},"| Nr                       | Status       | Tytuł                                                 |\n",[140,3056,3057],{"class":142,"line":185},[140,3058,3059],{},"| ------------------------ | ------------ | ----------------------------------------------------- |\n",[140,3061,3062],{"class":142,"line":216},[140,3063,3064],{},"| [0011](.\u002F_TASKS\u002F0011.md) | Zrealizowane | Ikony aplikacji i metadane web app                    |\n",[140,3066,3067],{"class":142,"line":247},[140,3068,3069],{},"| [0012](.\u002F_TASKS\u002F0012.md) | Zrealizowane | Resetowanie stanu Pinia store'ów przy wylogowaniu     |\n",[140,3071,3072],{"class":142,"line":616},[140,3073,3074],{},"| [0013](.\u002F_TASKS\u002F0013.md) | Zrealizowane | Konfigurowalna długość sesji (NUXT_SESSION_VALIDITY)  |\n",[140,3076,3077],{"class":142,"line":624},[140,3078,3079],{},"| [0014](.\u002F_TASKS\u002F0014.md) | Zrealizowane | Refaktor walidacji wejścia API (DTOs + helper-y)      |\n",[140,3081,3082],{"class":142,"line":641},[140,3083,3084],{},"| [0015](.\u002F_TASKS\u002F0015.md) | Zrealizowane | Warstwa bezpieczeństwa (nuxt-security)                |\n",[140,3086,3087],{"class":142,"line":649},[140,3088,3089],{},"| [0016](.\u002F_TASKS\u002F0016.md) | Zrealizowane | Centralna funkcja apiCall dla wywołań API frontend-u  |\n",[140,3091,3092],{"class":142,"line":657},[140,3093,3094],{},"| [0017](.\u002F_TASKS\u002F0017.md) | Zrealizowane | Globalny wskaźnik ładowania (appStatus + AppLoader)   |\n",[140,3096,3097],{"class":142,"line":665},[140,3098,3099],{},"| [0018](.\u002F_TASKS\u002F0018.md) | Zrealizowane | Refaktor systemu komunikatów (appStatus + AppToaster) |\n",[140,3101,3102],{"class":142,"line":704},[140,3103,3104],{},"| [0019](.\u002F_TASKS\u002F0019.md) | Zrealizowane | Testy integracyjne REST API                           |\n",[140,3106,3107],{"class":142,"line":730},[140,3108,3109],{},"| [0020](.\u002F_TASKS\u002F0020.md) | Zrealizowane | Profil — data urodzenia i wzrost                      |\n",[140,3111,3112],{"class":142,"line":738},[140,3113,3114],{},"| [0021](.\u002F_TASKS\u002F0021.md) | Zrealizowane | Statystyki — wskaźnik BMI w sekcji Waga               |\n",[140,3116,3117],{"class":142,"line":756},[140,3118,3119],{},"| [0022](.\u002F_TASKS\u002F0022.md) | Zrealizowane | Konfiguracja SMTP i serwis wysyłki e-mail             |\n",[140,3121,3122],{"class":142,"line":773},[140,3123,3124],{},"| [0023](.\u002F_TASKS\u002F0023.md) | Zrealizowane | Agent przypomnień e-mail                              |\n",[31,3126,3127,3128,3131,3132,3135],{},"Ciekawostka: część tych zadań (np. ",[41,3129,3130],{},"0014–0018",") to czysty refaktor pojawiający się w trakcie pracy z aplikacją — kiedy zauważam powtarzający się wzorzec albo widzę, że coś jest robione na trzy różne sposoby. Wcześniej takie rzeczy odkładałem ",[285,3133,3134],{},"na kiedyś",". W tym workflow są równorzędnymi zadaniami, które przechodzą tę samą drogę co nowe funkcjonalności.",[26,3137,3139,3140],{"id":3138},"skill-pomocniczy-commit-staged","Skill pomocniczy: ",[61,3141,3142],{},"commit-staged",[31,3144,3145,3146,3149,3150,64,3153,64,3156,3159],{},"Pracując w tym tempie szybko okazuje się, że pisanie sensownych ",[285,3147,3148],{},"commit message-y"," po polsku też zajmuje wymierny czas, a jeszcze częściej pojawia się pokusa, żeby napisać byle co (",[61,3151,3152],{},"update",[61,3154,3155],{},"fix",[61,3157,3158],{},"wip","). Czas na kolejnego specjalistę:",[131,3161,3163],{"className":547,"code":3162,"language":549,"meta":136,"style":136},"Przygotuj SKILL-a o nazwie **commit-staged**, który przygotuje _commit message_ w języku polskim, dla plików, które są w statusie _staged_, wykona _commit_ z przygotowanym opisem. Na końcu ZAWSZE ma pytać, czy wykonać _git push_.\n",[61,3164,3165],{"__ignoreMap":136},[140,3166,3167],{"class":142,"line":143},[140,3168,3162],{},[31,3170,3171,3172,3175,3176,3178],{},"Od momentu powstania tego SKILL-a, jedyne polecenie zatwierdzające zmiany w repozytorium to ",[61,3173,3174],{},"\u002Fcommit-staged",". Komunikaty są spójne stylistycznie, krótkie, w jednym języku, a finalne pytanie o ",[61,3177,3037],{}," chroni mnie przed nawykowym wysyłaniem niedokończonych zmian na zdalne repo.",[3180,3181],"hr",{},[26,3183,3185,3186],{"id":3184},"skill-5-dokumentacja-api-update-api-doc","SKILL #5: dokumentacja API — ",[61,3187,3188],{},"update-api-doc",[31,3190,3191,3192,494],{},"Kolejnym eksperymentem było sprawdzenie, czy agent jest w stanie samodzielnie utrzymywać dokumentację techniczną. Zlecam przygotowanie SKILL-a do dokumentacji ",[41,3193,3194],{},"REST API",[131,3196,3198],{"className":547,"code":3197,"language":549,"meta":136,"style":136},"Przygotuj SKILL-a o nazwie **update-api-doc**, który utworzy (jeśli nie istnieje) lub zaktualizuje (np. po zmianach w **backend**) plik `.\u002FAPI.md`. Skill ma dbać o dobrej jakości dokumentację wszystkich punktów _REST API endpoints_, zawierającą URL, metodę HTTP, przyjmowane dane wejściowe (z podziałem na body, url params, query params), czy sprawdza autentykację\u002Fautoryzację użytkownika oraz biznesowy opis operacji, które wykonuje przy wywołaniu.\n",[61,3199,3200],{"__ignoreMap":136},[140,3201,3202],{"class":142,"line":143},[140,3203,3197],{},[31,3205,3206,3207,3210,3211,3214],{},"Uruchomienie ",[61,3208,3209],{},"\u002Fupdate-api-doc"," dało mi kompletny plik ",[41,3212,3213],{},"API.md"," ze wszystkimi endpoint-ami: URL, metoda HTTP, wymagana autentykacja, schemat wejścia, opis biznesowy. Od tego momentu wystarczy wywołać ten SKILL po wprowadzeniu zmian w warstwie API, żeby dokumentacja była zsynchronizowana z kodem.",[26,3216,3218,3219],{"id":3217},"skill-6-porządkowanie-specyfikacji-cleanup-spec","SKILL #6: porządkowanie specyfikacji — ",[61,3220,3221],{},"cleanup-spec",[31,3223,3224,3225,3227,3228,3230,3231,52,3233,3235],{},"Po kilkudziesięciu iteracjach przez ",[61,3226,3003],{}," zaczęły pojawiać się ostrzeżenia w konsoli ",[41,3229,1875],{},": pliki ",[41,3232,2219],{},[41,3234,2620],{}," rozrosły się i zaczęły zajmować nieproporcjonalnie dużą część okna kontekstowego. Po krótkim audycie tych plików stało się jasne dlaczego:",[417,3237,3238,3249,3257],{},[420,3239,3240,3243,3244,3246,3247,1060],{},[285,3241,3242],{},"powielały się"," informacje między ",[41,3245,2219],{}," a ",[41,3248,2620],{},[420,3250,3251,3253,3254,3256],{},[285,3252,3242],{}," informacje z plikami zewnętrznymi (np. ",[41,3255,3213],{},"),",[420,3258,3259,3262],{},[285,3260,3261],{},"układ rozdziałów"," przestał być logiczny — np. „Usuwanie pomiarów” (funkcja użytkownika) wcisnęło się między „Skrypty administracyjne” a „Zmienne środowiskowe”.",[31,3264,3265],{},"Potrzebny był SKILL porządkujący:",[131,3267,3269],{"className":547,"code":3268,"language":549,"meta":136,"style":136},"Przygotuj SKILL-a o nazwie **cleanup-spec**, który porządkuje pliki `.\u002FSPEC.md` oraz `.\u002FCLAUDE.md`. Pliki szybko się rozrastają i agent rzuca ostrzeżeniami, że są zbyt duże, rozdmuchują kontekst. Nie ma sensu powielać tych samych informacji w obu plikach lub informacji, które są dostępne w plikach dodatkowych jak na przykład `.\u002FAPI.md`, który stanowi pełną dokumentację endpoint-ów REST API. Wystarczy przywołać plik zewnętrzny - wstawić link do pliku. Plik `.\u002FSPEC.md` powinien koncentrować się na danych zarządzanych w aplikacji oraz na stronie funkcjonalnej i biznesowej aplikacji, zawierać opisy funkcjonalności i realizowanych procesów biznesowych, czyli zawierać specyfikację z naciskiem na biznesową, a mniej techniczną. Zawartość powinna być lepiej uporządkowana, np. w tej chwili rozdział **Usuwanie pomiarów** (funkcja użytkownika), jest nielogicznie wciśnięty pomiędzy rozdziały **Skrypty administracyjne**, a **Zmienne środowiskowe**. Plik `.\u002FCLAUDE.md` powinien mieć zawartość zalecaną przez agenta _Claude Code_, ale wg mnie powinien koncentrować się na warstwie bardziej technicznej oraz stanowić pamięć projektu, czyli przechowywać wzorce projektowe, zalecenia architektoniczne. Nie powinien powielać informacji już dostępnych w pliku `.\u002FSPEC.md` oraz innych plikach projektu (jak `.\u002FAPI.md`), ale na pewno powinien na niego wskazywać z opisem co zawiera.\n",[61,3270,3271],{"__ignoreMap":136},[140,3272,3273],{"class":142,"line":143},[140,3274,3268],{},[31,3276,3206,3277,3280,3281,3284,3285,3287,3288,3291,3292,3294,3295,3297],{},[61,3278,3279],{},"\u002Fcleanup-spec"," zredukowało rozmiar obu plików o około ",[41,3282,3283],{},"40%",", uporządkowało strukturę rozdziałów i pozbyło się duplikatów. Ważniejsze niż statystyki — zmiana ról jest teraz jasna: ",[41,3286,2219],{}," mówi ",[285,3289,3290],{},"co"," robi aplikacja z perspektywy biznesowej, ",[41,3293,2620],{}," to projektowa pamięć agenta i warstwa techniczna, a pliki takie jak ",[41,3296,3213],{}," są przywoływane linkami zamiast kopiowane.",[26,3299,3301,3302],{"id":3300},"skill-7-instrukcja-użytkownika-update-user-guide","SKILL #7: instrukcja użytkownika — ",[61,3303,3304],{},"update-user-guide",[31,3306,3307,3308,3311],{},"Dokumentacja deweloperska to jedno, ale ",[41,3309,3310],{},"instrukcja dla użytkownika końcowego"," to zwykle zupełnie inna jakościowo historia: musi być prosta, czytelna, ze zrzutami ekranów. W praktyce takiej instrukcji prawie nigdy nie ma — pisze się ją na końcu projektu i niespecjalnie chętnie. Aplikacja jest dla mnie, więc instrukcja nie jest konieczna, ale to ciekawy test dla całego workflow.",[131,3313,3315],{"className":547,"code":3314,"language":549,"meta":136,"style":136},"Przygotuj SKILL-a o nazwie **update-user-guide**, który utworzy (jeśli nie istnieje) lub zaktualizuje (np. po zmianach w **frontend**) plik `.\u002FGUIDE.md`. SKILL ma dbać o dobrej jakości instrukcję dla użytkownika końcowego, zawierającą opis wszystkich funkcjonalności wraz ze zrzutami ekranów. SKILL ma tworzyć i używać użytkownika testowego, wykonać _seed_ tabel bazy danych, żeby wypełnić testowymi danymi (strona statystyk musi coś pokazywać), a zrzuty ekranu wykonać za pomocą _Playwright_.\n",[61,3316,3317],{"__ignoreMap":136},[140,3318,3319],{"class":142,"line":143},[140,3320,3314],{},[31,3322,3323],{},"Wynik mile mnie zaskoczył. Agent samodzielnie:",[417,3325,3326,3329,3336,3339,3350],{},[420,3327,3328],{},"utworzył testowego użytkownika,",[420,3330,3331,3332,3335],{},"wykonał ",[285,3333,3334],{},"seed"," tabel pomiarów (dziesiątki rekordów rozłożonych w czasie, żeby wykresy miały sens),",[420,3337,3338],{},"uruchomił aplikację,",[420,3340,3341,3342,3349],{},"przeszedł przez wszystkie ekrany za pomocą ",[41,3343,3344],{},[35,3345,3348],{"href":3346,"rel":3347},"https:\u002F\u002Fplaywright.dev\u002F",[111],"Playwright",", robiąc zrzuty,",[420,3351,3352],{},"napisał spójną instrukcję powiązaną tekstowo z odpowiednimi obrazkami.",[31,3354,3355,3356,3359,3360,3363],{},"Plik ",[41,3357,3358],{},"GUIDE.md"," spokojnie nadaje się do wyeksportowania jako PDF albo do podpięcia w samej aplikacji pod linkiem ",[41,3361,3362],{},"Instrukcja"," w menu głównym.",[26,3365,3367],{"id":3366},"generowanie-readmemd","Generowanie README.md",[31,3369,3370,3371,3374],{},"Na samym końcu, kiedy aplikacja, API i instrukcja dla użytkownika były już gotowe, poprosiłem agenta o jeszcze jedną rzecz — ",[285,3372,3373],{},"główną stronę projektu"," na GitHubie:",[131,3376,3378],{"className":547,"code":3377,"language":549,"meta":136,"style":136},"Mając wiedzę na temat tego projektu, przygotuj prosty plik **README.md**, który będzie stanowił główną stronę projektu dla GitHub repo.\n",[61,3379,3380],{"__ignoreMap":136},[140,3381,3382],{"class":142,"line":143},[140,3383,3377],{},[31,3385,3386,3387,3390,3391,64,3394,3397],{},"Tym razem nie tworzyłem SKILL-a — to była operacja jednorazowa. Agent przygotował kompletny ",[41,3388,3389],{},"README.md"," zawierający opis funkcji, stack technologiczny, instrukcję instalacji i uruchomienia, sposób zakładania kont użytkowników skryptem CLI oraz sekcję o aktualizacji zależności i bezpieczeństwie łańcucha dostaw (",[61,3392,3393],{},".ncurc.json",[61,3395,3396],{},".npmrc",").",[26,3399,3401],{"id":3400},"podsumowanie-workflow","Podsumowanie workflow",[31,3403,3404,3405,3407,3408,3411],{},"W finalnej formie cały mój ",[41,3406,2099],{}," dla projektu ",[41,3409,3410],{},"CPW"," sprowadza się do kilku ról i jednej pętli:",[31,3413,3414],{},[41,3415,3416],{},"Role (SKILL-e):",[417,3418,3419,3424,3433,3439,3445,3450,3459,3468],{},[420,3420,3421,3423],{},[61,3422,2664],{}," — planista, czyta specyfikację, układa zadania od bazy danych po frontend.",[420,3425,3426,3429,3430,56],{},[61,3427,3428],{},"do-task \u003Cnr>"," — wykonawca, realizuje pojedyncze zadanie zgodnie z planem w ",[61,3431,3432],{},"_TASKS\u002FNNNN.md",[420,3434,3435,3438],{},[61,3436,3437],{},"update-task \u003Cnr> \u003Copis>"," — korygujący, aktualizuje opis zadania i wprowadza zmianę w kodzie.",[420,3440,3441,3444],{},[61,3442,3443],{},"update-spec \u003Copis>"," — analityk biznesowy, aktualizuje specyfikację o nowe wymagania.",[420,3446,3447,3449],{},[61,3448,3221],{}," — porządkowy, dba o higienę plików specyfikacji.",[420,3451,3452,3454,3455,3458],{},[61,3453,3188],{}," — ",[285,3456,3457],{},"technical writer"," od API.",[420,3460,3461,3454,3463,3465,3466,3397],{},[61,3462,3304],{},[285,3464,3457],{}," od instrukcji użytkownika (ze zrzutami z ",[41,3467,3348],{},[420,3469,3470,3472],{},[61,3471,3142],{}," — sekretarz, opisuje i zatwierdza zmiany w repozytorium.",[31,3474,3475],{},[41,3476,3477],{},"Pętla rozwojowa:",[131,3479,3482],{"className":3480,"code":3481,"language":891},[889],"Główna:\n  pomysł → \u002Fupdate-spec → \u002Fupdate-tasks-list → \u002Fdo-task → (\u002Fupdate-task)* → \u002Fcommit-staged\n\nPomocnicza:\n  ↘ \u002Fupdate-api-doc, \u002Fupdate-user-guide (regularnie)\n  ↘ \u002Fcleanup-spec (okresowo)\n",[61,3483,3481],{"__ignoreMap":136},[31,3485,3486,3487,64,3490,64,3493,52,3498,3501],{},"Moja rola w tej pętli to ",[41,3488,3489],{},"architektura",[41,3491,3492],{},"decyzje koncepcyjne",[41,3494,3495,3496],{},"recenzja przez ",[61,3497,2865],{},[41,3499,3500],{},"testy ręczne"," w przeglądarce. Agent wykonuje wszystko, co da się jednoznacznie opisać — a okazuje się, że da się opisać znacznie więcej, niż na pierwszy rzut oka się wydaje.",[3503,3504,3505],"warning",{},[31,3506,3507,3508,3511,3512,3514],{},"Workflow nie zwalnia z myślenia. Agent generuje kod zgodny z opisem zadania, ale to nie znaczy, że jest to ",[41,3509,3510],{},"najlepszy"," kod. Jakość decyzji architektonicznych nadal zależy od człowieka — agent doskonale wykonuje to, co mu powiesz, ale nie powie ci, że powinieneś powiedzieć coś innego. ",[61,3513,2865],{}," przed każdym commit-em to nadal podstawa, a nie formalność.",[26,3516,2025],{"id":2024},[31,3518,3519],{},"Trzy obserwacje, które wynoszę z tego projektu:",[417,3521,3522,3528,3542],{},[420,3523,3524,3527],{},[41,3525,3526],{},"Wyspecjalizowany agent bije agenta uniwersalnego."," Te same modele i to samo narzędzie, ale rozdzielenie ról przez SKILL-e dało jakościowo lepsze wyniki niż próby zmuszania jednej sesji do robienia wszystkiego naraz. Każdy SKILL ma jasne kryteria sukcesu, jasne pliki, na których pracuje, i jasne ograniczenia.",[420,3529,3530,3533,3534,3536,3537,3034,3539,3541],{},[41,3531,3532],{},"Specyfikacja musi być żywa."," Workflow, w którym ",[41,3535,2219],{}," jest pisany raz na początku i nigdy nie odświeżany, rozjeżdża się z kodem w ciągu tygodni. ",[61,3538,3003],{},[61,3540,3279],{}," to nie luksus, tylko warunek konieczny, żeby reszta procesu w ogóle miała sens po kilkudziesięciu iteracjach.",[420,3543,3544,3547,3548,3551,3552,3555],{},[41,3545,3546],{},"Małe zadania, częste commit-y."," Konwencja ",[61,3549,3550],{},"1 zadanie = 1 funkcjonalność = 1 commit"," (z opcjonalnymi commit-ami od ",[61,3553,3554],{},"\u002Fupdate-task",") daje czytelną historię i pozwala szybko cofać się do dowolnego stanu projektu. Bez tego nie da się recenzować pracy agenta na tej skali.",[31,3557,3558,3559,3561,3562,3565,3566,3569,3570,3573,3574,64,3576,3579,3580,3582],{},"Aplikacja ",[41,3560,3410],{}," jest niekomercyjna i pisana wyłącznie dla mnie, więc jako ",[285,3563,3564],{},"projekt produktowy"," jest mało ekscytująca. Jako ",[285,3567,3568],{},"ćwiczenie z workflow"," była bardzo wartościowa — w kilku wieczorach zbudowałem działającą aplikację z ",[41,3571,3572],{},"23 zadaniami",", pełną dokumentacją techniczną, dokumentacją ",[41,3575,3194],{},[41,3577,3578],{},"instrukcją użytkownika"," ze zrzutami ekranów i wzorcami bezpieczeństwa łańcucha dostaw. Wszystko to z ",[61,3581,2938],{},"-iem, w którym każdy commit ma sens.",[26,3584,3586],{"id":3585},"materiały-do-pobrania-podglądu","Materiały do pobrania \u002F podglądu",[31,3588,3589,3590,3592],{},"Wszystkie SKILL-e opisane w tym artykule oraz pliki dokumentacji wygenerowane w projekcie ",[41,3591,3410],{}," udostępniam do swobodnego użycia:",[417,3594,3595,3609,3618,3627],{},[420,3596,3597,3604,3605,3608],{},[41,3598,3599],{},[35,3600,3603],{"href":3601,"rel":3602,"external":136},"https:\u002F\u002Fgithub.com\u002FArturWilczek\u002Fclaude-skills-1",[111],"Komplet SKILL-i Claude Code"," — publiczne repozytorium z wszystkimi 8 SKILL-ami jako zestawem plików ",[61,3606,3607],{},"SKILL.md"," w odpowiedniej strukturze katalogów.",[420,3610,3611,3617],{},[41,3612,3613],{},[35,3614,3616],{"href":3615},"\u002Fprevs\u002Fp0002","Przykładowy README.md"," — wygenerowany na końcu projektu.",[420,3619,3620,3626],{},[41,3621,3622],{},[35,3623,3625],{"href":3624},"\u002Fprevs\u002Fp0003","Przykładowy API.md"," — pełna dokumentacja REST API.",[420,3628,3629,3635,3636,56],{},[41,3630,3631],{},[35,3632,3634],{"href":3633},"\u002Fprevs\u002Fp0004","Przykładowy GUIDE.md"," — instrukcja użytkownika końcowego ze zrzutami z ",[41,3637,3348],{},[31,3639,3640,3641,3644],{},"Wystarczy skopiować SKILL-e do katalogu ",[61,3642,3643],{},".claude\u002Fskills\u002F"," we własnym projekcie i można od razu zacząć pracę w tym samym workflow.",[2065,3646,3647],{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":136,"searchDepth":185,"depth":185,"links":3649},[3650,3651,3652,3653,3654,3655,3657,3658,3660,3662,3664,3665,3667,3669,3671,3673,3674,3675,3676],{"id":28,"depth":185,"text":29},{"id":2154,"depth":185,"text":2155},{"id":2201,"depth":185,"text":2202},{"id":2600,"depth":185,"text":2601},{"id":2636,"depth":185,"text":2637},{"id":2660,"depth":185,"text":3656},"SKILL #1: planista zadań — update-tasks-list",{"id":2744,"depth":185,"text":2745},{"id":2851,"depth":185,"text":3659},"SKILL #2: wykonawca — do-task",{"id":2921,"depth":185,"text":3661},"SKILL #3: korygujący — update-task",{"id":2959,"depth":185,"text":3663},"SKILL #4: aktualizacja specyfikacji — update-spec",{"id":2988,"depth":185,"text":2989},{"id":3138,"depth":185,"text":3666},"Skill pomocniczy: commit-staged",{"id":3184,"depth":185,"text":3668},"SKILL #5: dokumentacja API — update-api-doc",{"id":3217,"depth":185,"text":3670},"SKILL #6: porządkowanie specyfikacji — cleanup-spec",{"id":3300,"depth":185,"text":3672},"SKILL #7: instrukcja użytkownika — update-user-guide",{"id":3366,"depth":185,"text":3367},{"id":3400,"depth":185,"text":3401},{"id":2024,"depth":185,"text":2025},{"id":3585,"depth":185,"text":3586},"2026-06-02","Buduję zespół agentów \u002F specjalistów Claude Code, ustalam flow procesu produkcji i utrzymania nowej aplikacji.","\u002Fpics\u002F0004.png",{"order":247},{"title":2099,"description":3678},{"loc":37},"blog\u002Fai-driven-development-workflow","0rtmlzc-pXSpm9CUe4hMCO4y11PijZ_99GJp5lQHPYE",{"id":3686,"title":3687,"author":21,"body":3688,"date":4696,"description":4697,"draft":2088,"extension":2089,"image":4698,"meta":4699,"minRead":6,"navigation":10,"path":4700,"seo":4701,"sitemap":4702,"stem":4703,"__hash__":4704},"blog\u002Fblog\u002Fmakieta-nuxt-supabase.md","Dwa dni robocze, jeden działający prototyp",{"type":23,"value":3689,"toc":4679},[3690,3694,3708,3711,3714,3717,3721,3728,3757,3767,3771,3790,3801,3812,3820,3825,3859,3875,4085,4089,4111,4118,4127,4131,4134,4160,4169,4173,4258,4262,4301,4305,4350,4354,4375,4379,4437,4441,4556,4560,4616,4620,4658,4662,4665,4676],[26,3691,3693],{"id":3692},"po-co-prototyp-lub-mvp","Po co prototyp lub MVP?",[31,3695,3696,3699,3700,3707],{},[41,3697,3698],{},"MVP"," - Minimum Viable Product - najprostsza, funkcjonalna wersja aplikacji, która zawiera jedynie podstawowe funkcje pozwalające rozwiązać kluczowy problem użytkowników. Pojęcie popularne w świecie ",[41,3701,3702],{},[35,3703,3706],{"href":3704,"rel":3705},"https:\u002F\u002Fpl.wikipedia.org\u002Fwiki\u002FOprogramowanie_jako_us%C5%82uga",[111],"SaaS",", ale ma też zastosowanie przy podejściu do tworzenia aplikacji z szybkim prototypowaniem.",[31,3709,3710],{},"Z doświadczenia wiem, że nawet po wielu godzinach spotkań z przyszłym użytkownikiem aplikacji, przygotowywania koncepcji i architektury rozwiązania, kluczowe pomysły i najwięcej uwag do produktu pojawia się po oddaniu aplikacji do testów, a nawet częściej po oddaniu aplikacji do użytku.",[31,3712,3713],{},"Dzisiaj mamy do dyspozycji narzędzia i technologie, które pozwalają, w bardzo krótkim czasie, przygotować makietę aplikacji, w której użytkownik może wykonywać lub symulować rzeczywiste operacje biznesowe, zgodne z założeniami procesu, który ta aplikacja ma obsługiwać i wspierać. W ten sposób użytkownik bierze czynny udział w procesie tworzenia, a wszystkie zgłaszane rozwiązania lub modyfikacje mogą zostać sprawnie zaimplementowane i oddane do kolejnych testów.",[31,3715,3716],{},"Jeśli pracujemy dla nowego, zewnętrznego klienta, który jeszcze nie do końca nam ufa i nie chce ponosić dużych kosztów na początku współpracy, możemy szybko zdefiniować główne założenia projektowe, równie szybko przygotować prototyp i bez ingerencji w jego infrastrukturę, udostępnić prototyp na własnej platformie (jeśli takową dysponujemy) lub skorzystać z jednej z wielu publicznie dostępnych platform hostingowych. W przypadku platform Internetowych, makietę aplikacji błyskawicznie wypełniamy fikcyjnymi i sztucznie wygenerowanymi danymi, nie narażając klienta na wyciek poufnych danych biznesowych.",[26,3718,3720],{"id":3719},"co-udało-się-zrobić-w-tak-krótkim-czasie","Co udało się zrobić w tak krótkim czasie?",[31,3722,3723,3724,3727],{},"Na potrzeby tego artykułu poświęciłem ",[41,3725,3726],{},"dwa dni robocze",", żeby przygotować pokazowy prototyp aplikacji, która z założenia może pracować w rejestracji lub sekretariacie placówki medycznej. W tak krótkim czasie udało się wdrożyć funkcje takie jak:",[417,3729,3730,3733,3736,3739,3742,3745,3748,3751,3754],{},[420,3731,3732],{},"lista specjalizacji medycznych",[420,3734,3735],{},"dodawanie nowych lekarzy",[420,3737,3738],{},"prezentacja lekarzy w formie tabeli z możliwością sortowania\u002Ffiltrowania",[420,3740,3741],{},"dodawanie nowych pacjentów",[420,3743,3744],{},"prezentacja pacjentów w formie tabeli z możliwością sortowania\u002Ffiltrowania",[420,3746,3747],{},"definiowanie dostępności lekarzy",[420,3749,3750],{},"rezerwowanie wizyt pacjentów",[420,3752,3753],{},"wizualizacja danych związanych z lekarzem: dane podstawowe, dostępność, wizyty zaplanowane i archiwalne, pacjenci powiązani relacją pacjent-lekarz rodzinny, rejestr zdarzeń",[420,3755,3756],{},"wizualizacja danych związanych z pacjentem: dane podstawowe, wizyty zaplanowane i archiwalne, rejestr zdarzeń",[31,3758,3759,3760,3766],{},"Na końcu tego artykułu udostępniam ",[41,3761,3762],{},[35,3763,3765],{"href":3764},"#materia%C5%82-wideo","materiał wideo",", na którym demonstruję co potrafi prototyp.",[26,3768,3770],{"id":3769},"jak-to-w-ogóle-możliwe-w-tak-krótkim-czasie","Jak to w ogóle możliwe w tak krótkim czasie?",[31,3772,3773,3774,3777,3778,3784,3785,56],{},"Takie możliwości dają oczywiście duże modele językowe, z których, coraz sprawniej, korzystają ",[41,3775,3776],{},"agenci \u002F asystenci AI"," dedykowani do wykonywania prac programistycznych. Osobiście używam ",[41,3779,3780],{},[35,3781,3783],{"href":2114,"rel":3782},[111],"GitHub Copilot Pro"," zintegrowanego z ",[41,3786,3787],{},[35,3788,2124],{"href":2122,"rel":3789},[111],[31,3791,3792,3793,3800],{},"Stosując tradycyjne techniki programistyczne, nie jestem w stanie dostarczyć prototypu w tak krótkim czasie. Mogę zaryzykować stwierdzenie, że tak samo trudno byłoby uzyskać podobny efekt stosując platformę typu ",[41,3794,3795],{},[35,3796,3799],{"href":3797,"rel":3798},"https:\u002F\u002Fpl.wikipedia.org\u002Fwiki\u002FPlatforma_niskokodowa",[111],"low-code",", w której, co prawda, buduje się szybko podstawowe funkcjonalności z gotowych klocków, ale przy implementacji bardziej skomplikowanej logiki biznesowej, na drodze stają ograniczenia lub specyfika platformy.",[31,3802,3803,3804,3811],{},"Z pomocą przychodzą też coraz bardziej popularne protokół i serwery ",[41,3805,3806],{},[35,3807,3810],{"href":3808,"rel":3809},"https:\u002F\u002Fpl.wikipedia.org\u002Fwiki\u002FModel_Context_Protocol#",[111],"MCP",", które dają agentowi AI dostęp, na przykład do:",[417,3813,3814,3817],{},[420,3815,3816],{},"aktualnej dokumentacji narzędzi lub technologii, których używam w czasie przygotowywania prototypu",[420,3818,3819],{},"narzędzi platform bazodanowych, które pozwalają kreować obiekty DB stosując język naturalny",[31,3821,3822,3823,494],{},"W tym projekcie korzystałem z trzech serwerów ",[41,3824,3810],{},[417,3826,3827,3836,3845],{},[420,3828,3829,3830],{},"dokumentacja ",[41,3831,3832],{},[35,3833,2892],{"href":3834,"rel":3835},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002F4.x\u002Fguide\u002Fai\u002Fmcp",[111],[420,3837,3829,3838],{},[41,3839,3840],{},[35,3841,3844],{"href":3842,"rel":3843},"https:\u002F\u002Fui.nuxt.com\u002Fdocs\u002Fgetting-started\u002Fai\u002Fmcp",[111],"Nuxt UI",[420,3846,3847,3848,303,3855,3858],{},"narzędzia platformy bazodanowej ",[41,3849,3850],{},[35,3851,3854],{"href":3852,"rel":3853},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fgetting-started\u002Fmcp",[111],"Supabase",[41,3856,3857],{},"PostgreSQL"," w chmurze)",[31,3860,3861,3862,3867,3868,3871,3872,494],{},"Konfiguracja w ",[41,3863,3864],{},[35,3865,2124],{"href":2122,"rel":3866},[111]," jest prosta i wymaga przygotowania pliku ",[41,3869,3870],{},"mcp.json"," w folderze ",[41,3873,3874],{},".vscode",[131,3876,3881],{"className":3877,"code":3878,"filename":3879,"language":3880,"meta":136,"style":136},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"servers\": {\n    \"NUXT UI\": {\n      \"type\": \"http\",\n      \"url\": \"https:\u002F\u002Fui.nuxt.com\u002Fmcp\"\n    },\n\n    \"NUXT\": {\n      \"type\": \"http\",\n      \"url\": \"https:\u002F\u002Fnuxt.com\u002Fmcp\"\n    },\n\n    \"SUPABASE\": {\n      \"type\": \"http\",\n      \"url\": \"https:\u002F\u002Fmcp.supabase.com\u002Fmcp?project_ref=klnuspxlvrxjhudrxcro\"\n    }\n  }\n}\n",".vscode\\mcp.json","json",[61,3882,3883,3888,3903,3917,3939,3957,3962,3966,3979,3997,4014,4018,4022,4035,4053,4070,4075,4080],{"__ignoreMap":136},[140,3884,3885],{"class":142,"line":143},[140,3886,3887],{"class":146},"{\n",[140,3889,3890,3893,3896,3898,3900],{"class":142,"line":185},[140,3891,3892],{"class":146},"  \"",[140,3894,3895],{"class":154},"servers",[140,3897,161],{"class":146},[140,3899,494],{"class":146},[140,3901,3902],{"class":146}," {\n",[140,3904,3905,3908,3911,3913,3915],{"class":142,"line":216},[140,3906,3907],{"class":146},"    \"",[140,3909,3910],{"class":363},"NUXT UI",[140,3912,161],{"class":146},[140,3914,494],{"class":146},[140,3916,3902],{"class":146},[140,3918,3919,3922,3925,3927,3929,3931,3934,3936],{"class":142,"line":247},[140,3920,3921],{"class":146},"      \"",[140,3923,3924],{"class":1056},"type",[140,3926,161],{"class":146},[140,3928,494],{"class":146},[140,3930,1942],{"class":146},[140,3932,3933],{"class":164},"http",[140,3935,161],{"class":146},[140,3937,3938],{"class":146},",\n",[140,3940,3941,3943,3946,3948,3950,3952,3955],{"class":142,"line":616},[140,3942,3921],{"class":146},[140,3944,3945],{"class":1056},"url",[140,3947,161],{"class":146},[140,3949,494],{"class":146},[140,3951,1942],{"class":146},[140,3953,3954],{"class":164},"https:\u002F\u002Fui.nuxt.com\u002Fmcp",[140,3956,1948],{"class":146},[140,3958,3959],{"class":142,"line":624},[140,3960,3961],{"class":146},"    },\n",[140,3963,3964],{"class":142,"line":641},[140,3965,574],{"emptyLinePlaceholder":10},[140,3967,3968,3970,3973,3975,3977],{"class":142,"line":649},[140,3969,3907],{"class":146},[140,3971,3972],{"class":363},"NUXT",[140,3974,161],{"class":146},[140,3976,494],{"class":146},[140,3978,3902],{"class":146},[140,3980,3981,3983,3985,3987,3989,3991,3993,3995],{"class":142,"line":657},[140,3982,3921],{"class":146},[140,3984,3924],{"class":1056},[140,3986,161],{"class":146},[140,3988,494],{"class":146},[140,3990,1942],{"class":146},[140,3992,3933],{"class":164},[140,3994,161],{"class":146},[140,3996,3938],{"class":146},[140,3998,3999,4001,4003,4005,4007,4009,4012],{"class":142,"line":665},[140,4000,3921],{"class":146},[140,4002,3945],{"class":1056},[140,4004,161],{"class":146},[140,4006,494],{"class":146},[140,4008,1942],{"class":146},[140,4010,4011],{"class":164},"https:\u002F\u002Fnuxt.com\u002Fmcp",[140,4013,1948],{"class":146},[140,4015,4016],{"class":142,"line":704},[140,4017,3961],{"class":146},[140,4019,4020],{"class":142,"line":730},[140,4021,574],{"emptyLinePlaceholder":10},[140,4023,4024,4026,4029,4031,4033],{"class":142,"line":738},[140,4025,3907],{"class":146},[140,4027,4028],{"class":363},"SUPABASE",[140,4030,161],{"class":146},[140,4032,494],{"class":146},[140,4034,3902],{"class":146},[140,4036,4037,4039,4041,4043,4045,4047,4049,4051],{"class":142,"line":756},[140,4038,3921],{"class":146},[140,4040,3924],{"class":1056},[140,4042,161],{"class":146},[140,4044,494],{"class":146},[140,4046,1942],{"class":146},[140,4048,3933],{"class":164},[140,4050,161],{"class":146},[140,4052,3938],{"class":146},[140,4054,4055,4057,4059,4061,4063,4065,4068],{"class":142,"line":773},[140,4056,3921],{"class":146},[140,4058,3945],{"class":1056},[140,4060,161],{"class":146},[140,4062,494],{"class":146},[140,4064,1942],{"class":146},[140,4066,4067],{"class":164},"https:\u002F\u002Fmcp.supabase.com\u002Fmcp?project_ref=klnuspxlvrxjhudrxcro",[140,4069,1948],{"class":146},[140,4071,4072],{"class":142,"line":790},[140,4073,4074],{"class":146},"    }\n",[140,4076,4077],{"class":142,"line":1409},[140,4078,4079],{"class":146},"  }\n",[140,4081,4082],{"class":142,"line":1424},[140,4083,4084],{"class":146},"}\n",[26,4086,4088],{"id":4087},"czy-faktycznie-zaczynałem-od-zera","Czy faktycznie zaczynałem od zera?",[31,4090,4091,4092,4098,4099,4102,4103,4106,4107,4110],{},"Jeśli chodzi o platformę ",[41,4093,4094],{},[35,4095,3854],{"href":4096,"rel":4097},"https:\u002F\u002Fsupabase.com",[111],", to rzeczywiście tak - utworzyłem nowy, pusty projekt. Wszystkie obiekty DB, takie jak: tabele, polityki ",[285,4100,4101],{},"RLS",", funkcje, ",[285,4104,4105],{},"trigger-y",", funkcje typu ",[285,4108,4109],{},"edge",", tworzyłem wydając agentowi polecenia w języku naturalnym. To samo jeśli chodzi o wypełnianie tabel fikcyjnymi danymi.",[3503,4112,4113],{},[31,4114,4115],{},[285,4116,4117],{},"Na potrzeby artykułu i aplikacji pokazowej, stosuję polskie nazwy tabel i pól. W realnym świecie raczej stosuje się nazewnictwo w języku angielskim.",[31,4119,4120,4121,4124,4125,56],{},"Jeśli chodzi o ",[41,4122,4123],{},"frontend web","-owy, to na start użyłem własnego szablonu, który jest minimalnym szkieletem interfejsu użytkownika i zawiera tylko górną belkę, lewy panel menu (pusty), główną przestrzeń roboczą (pustą), okno logowania i rejestracji użytkownika oraz podstawową integrację z platformą ",[41,4126,3854],{},[26,4128,4130],{"id":4129},"na-czym-polegała-moja-praca","Na czym polegała moja praca?",[31,4132,4133],{},"Głównie na wymyślaniu architektury, definiowaniu zadań dla mojego agenta oraz weryfikowaniu i testowaniu wyników jego pracy.",[31,4135,4136,4137,4140,4141,303,4144,4147,4148,4151,4152,4155,4156,4159],{},"Wszystkie polecenia dla agenta zapisywałem w notatkach i uzbierało się tego około ",[41,4138,4139],{},"100"," zadań, co przy ",[41,4142,4143],{},"dwóch dniach",[41,4145,4146],{},"960"," minut) intensywnych prac, daje średnio ponad ",[41,4149,4150],{},"9,5"," minuty na jedno zadanie. Zdecydowana większość prompt-ów była jednozdaniowymi, precyzyjnymi poleceniami, których napisanie zajmowało mniej niż ",[41,4153,4154],{},"1"," minutę, wykonanie agenta to ",[41,4157,4158],{},"2-3"," minuty, reszta to była moja weryfikacja i szybkie testy. Zdarzały się też bardziej rozbudowane polecenia, które, na przykład, definiowały skomplikowaną logikę i nie miało sensu dzielić ich na mniejsze porcje, co dawało asystentowi lepsze trzymanie kontekstu. Tak na marginesie, agent sam dzielił większe zadanie na mniejsze podzadania oraz ustalał prawidłową kolejność ich realizacji.",[31,4161,4162,4163,4165,4166,56],{},"Poniżej kilka ciekawszych zadań \u002F prompt-ów, zarówno tych dla bazy danych ",[41,4164,3854],{}," oraz frontend-u ",[41,4167,4168],{},"Nuxt \u002F Nuxt UI",[864,4170,4172],{"id":4171},"utworzenie-dwóch-tabel-w-db-zdefiniowanie-relacji-oraz-insert-testowych-danych","Utworzenie dwóch tabel w DB, zdefiniowanie relacji oraz INSERT testowych danych:",[4174,4175,4176,4179,4214,4216,4243,4255],"blockquote",{},[31,4177,4178],{},"Potrzebuję w bazie danych nowej tabeli:",[417,4180,4181,4187,4201],{},[420,4182,4183,4184],{},"nazwa tabeli: ",[41,4185,4186],{},"lekarze",[420,4188,4189,4190,64,4193,4196,4197,4200],{},"pola: ",[41,4191,4192],{},"id",[41,4194,4195],{},"imie"," (text, not null), ",[41,4198,4199],{},"nazwisko"," (text, not null)",[420,4202,4203,4204,64,4207,64,4210,4213],{},"włącz politykę RLS: ",[41,4205,4206],{},"SELECT",[41,4208,4209],{},"INSERT",[41,4211,4212],{},"UPDATE"," dla zalogowanych użytkowników",[31,4215,4178],{},[417,4217,4218,4224,4235,4240],{},[420,4219,4220,4221],{},"nazwa: ",[41,4222,4223],{},"specjalizacje",[420,4225,4189,4226,64,4228,4196,4231,4234],{},[41,4227,4192],{},[41,4229,4230],{},"specjalizacja",[41,4232,4233],{},"is_active"," (boolean, domyślnie true)",[420,4236,4237,4238,4213],{},"włącz politykę RLS: tylko ",[41,4239,4206],{},[420,4241,4242],{},"wypełnij tabelę 30 specjalizacjami lekarskimi",[31,4244,4245,4246,4248,4249,4252,4253,56],{},"Do tabeli ",[41,4247,4186],{}," dodaj pole ",[41,4250,4251],{},"id_specjalizacji"," jako klucz obcy do tabeli ",[41,4254,4223],{},[31,4256,4257],{},"Dodaj 100 lekarzy do tabeli lekarze z różnych specjalizacji.",[864,4259,4261],{"id":4260},"rozbudowa-strony-z-tabelą-specjalizacji","Rozbudowa strony z tabelą specjalizacji:",[4174,4263,4264,4270],{},[31,4265,4266,4267,494],{},"Strona ",[41,4268,4269],{},"\u002Fspecjalizacje",[417,4271,4272,4282,4290,4296],{},[420,4273,4274,4275,4278,4279],{},"w kolumnie ",[41,4276,4277],{},"Status"," wartość prezentuje komponent ",[41,4280,4281],{},"UBadge",[420,4283,4284,4285,4287,4288],{},"zostaw go, ale zrób z niego link, którym można zmienić (toggle) wartość w polu ",[41,4286,4233],{}," tabeli ",[41,4289,4223],{},[420,4291,4292,4293],{},"obsługa zmiany wartości, komunikaty podobnie jak na stronie ",[41,4294,4295],{},"\u002Flekarze",[420,4297,4298,4299],{},"dodaj możliwość sortowania po kolumnie ",[41,4300,4277],{},[864,4302,4304],{"id":4303},"nowa-strona-ze-szczegółami-lekarza","Nowa strona ze szczegółami lekarza:",[4174,4306,4307,4313,4337],{},[31,4308,4309,4310,494],{},"Nowa strona ",[41,4311,4312],{},"\u002Flekarze\u002F:id",[417,4314,4315,4318,4328,4331,4334],{},[420,4316,4317],{},"dodaj stronę",[420,4319,4320,4321,4323,4324,4327],{},"w tabeli lekarzy na stronie ",[41,4322,4295],{}," jest kolumna ",[41,4325,4326],{},"ID"," - zrób z niej link do nowej strony",[420,4329,4330],{},"na stronie formularz read-only z danymi lekarza",[420,4332,4333],{},"poniżej formularza, tabela zdarzeń związanych z lekarzem",[420,4335,4336],{},"tabela zdarzeń w prostej formie, sortowanie po dacie malejąco, bez możliwości sortowanie i filtrowania",[31,4338,4339,4340,4343,4344,3871,4347,4349],{},"Nie działają linki do nowej strony. Może trzeba plik ",[41,4341,4342],{},"lekarze.vue"," zmienić na ",[41,4345,4346],{},"index.vue",[41,4348,4186],{},"?",[864,4351,4353],{"id":4352},"korekta-formularza-dodawania-nowego-lekarza","Korekta formularza dodawania nowego lekarza:",[4174,4355,4356],{},[31,4357,4358,4359,4362,4363,4366,4367,4370,4371,4374],{},"Zmień układ formularza tak aby pola pojawiały się na przemiennie w dwóch kolumnach o tej samej szerokości (użyj ",[41,4360,4361],{},"grid",").\nPola typu ",[41,4364,4365],{},"input"," czy ",[41,4368,4369],{},"select"," powinny zajmować całą szerokość kolumny.\nWszystkie błędy i sukcesy pokazuj za pomocą ",[41,4372,4373],{},"toast",".\nRozbuduj walidację: z listy lekarzy rodzinnych musi być wybrana pozycja.\nLista rozwijana lekarza rodzinnego jest bardzo długa. Dodaj możliwość filtrowania po nazwisku.",[864,4376,4378],{"id":4377},"nowa-tabela-z-mechanizmem-seed","Nowa tabela z mechanizmem seed:",[4174,4380,4381,4384],{},[31,4382,4383],{},"Nowa tabela w bazie danych:",[417,4385,4386,4391,4399,4403,4426],{},[420,4387,4183,4388],{},[41,4389,4390],{},"dni",[420,4392,4189,4393,64,4395,4398],{},[41,4394,4192],{},[41,4396,4397],{},"dzien"," (text, not null, unique)",[420,4400,4237,4401,4213],{},[41,4402,4206],{},[420,4404,4405,4406,4408,4409],{},"utwórz mechanizm ",[41,4407,3334],{}," dla tabeli, tak żeby wstawiał lub aktualizował następujące rekordy:\n",[417,4410,4411,4414,4417,4420,4423],{},[420,4412,4413],{},"id: 1; dzien: Poniedziałek",[420,4415,4416],{},"id: 2; dzien: Wtorek",[420,4418,4419],{},"id: 3; dzien: Środa",[420,4421,4422],{},"id: 4; dzien: Czwartek",[420,4424,4425],{},"id: 5; dzien: Piątek",[420,4427,4428,4429,4287,4431,4433,4434,56],{},"Mechanizm ",[41,4430,3334],{},[41,4432,4390],{}," zrób jako funkcja bazy danych podobnie jak funkcja ",[41,4435,4436],{},"seed_event_types",[864,4438,4440],{"id":4439},"funkcja-brzegowa-realizująca-logikę-planowania-wizyty-lekarskiej","Funkcja brzegowa realizująca logikę planowania wizyty lekarskiej:",[4174,4442,4443,4446,4464,4469,4543,4548],{},[31,4444,4445],{},"Nowa funkcja brzegowa (edge function):",[417,4447,4448,4453],{},[420,4449,4220,4450],{},[41,4451,4452],{},"zaplanuj_wizyte",[420,4454,4455,4456,64,4459,64,4462],{},"argumenty: ",[41,4457,4458],{},"lekarz_id",[41,4460,4461],{},"pacjent_id",[41,4463,4397],{},[31,4465,4466,4467,494],{},"Logika funkcji ",[41,4468,4452],{},[417,4470,4471,4474,4480,4485,4488,4491,4496,4502,4509,4520,4526,4535],{},[420,4472,4473],{},"sprawdź, czy wywołanie funkcji jest w kontekście zalogowanego użytkownika, jeśli nie to błąd",[420,4475,4476,4477,4479],{},"sprawdź, czy istnieje lekarz o ",[41,4478,4458],{},", jeśli nie to błąd",[420,4481,4482,4483,4479],{},"sprawdź, czy istnieje pacjent o ",[41,4484,4461],{},[420,4486,4487],{},"dzień może być dzisiaj lub w przyszłości, jeśli nie to błąd",[420,4489,4490],{},"na podstawie daty ustal jaki to dzień tygodnia (Poniedziałek to 1)",[420,4492,4493,4494,4479],{},"sprawdź, czy istnieje taki dzień w tabeli ",[41,4495,4390],{},[420,4497,4498,4499,4479],{},"sprawdź, czy lekarz ma ten dzień tygodnia ustaloną dostępność w tabeli ",[41,4500,4501],{},"dostepnosc_lekarza",[420,4503,4504,4505,4508],{},"odczytaj wszystkie rekordy z tabeli ",[41,4506,4507],{},"wizyty"," dla lekarza na tę datę posortowane rosnąco po godzinie",[420,4510,4511,4512,4515,4516,4519],{},"ustal pierwszą godzinę dostępności lekarza bazując na zdefiniowanej dostępności w ",[41,4513,4514],{},"dostępnosc_lekarza",", biorąc pod uwagę godzinę startową oraz czas potrzebny na pacjenta (pole ",[41,4517,4518],{},"czas_na_pacjenta","); jeśli nie udało się ustalić wolnej godziny (np. z powodu braku miejsc) to błąd",[420,4521,4522,4523,4525],{},"dodaj rekord do tabeli ",[41,4524,4507],{},", w pole godzina wstaw ustaloną godzinę pierwszej możliwej wizyty",[420,4527,4522,4528,64,4531,4534],{},[41,4529,4530],{},"lekarze_zdarzenia",[41,4532,4533],{},"zdarzenie_id"," = 5, opis = Pacjent: Nazwisko Imię pacjenta + data i godzina wizyty",[420,4536,4522,4537,64,4540,4542],{},[41,4538,4539],{},"pacjenci_zdarzenia",[41,4541,4533],{}," = 5, opis = Pacjent: Nazwisko Imię lekarza + data i godzina wizyty",[31,4544,4466,4545,4547],{},[41,4546,4452],{}," - rozbudowa:",[417,4549,4550,4553],{},[420,4551,4552],{},"sprawdzaj, czy lekarz jest aktywny, jeśli nie to błąd",[420,4554,4555],{},"dodaj kontrolę, która pozwala na zaplanowanie pacjentowi tylko jednej wizyty w danym dniu",[864,4557,4559],{"id":4558},"szybka-migracja-zarządzania-stanem-do-pinia-store","Szybka migracja zarządzania stanem do Pinia Store:",[4174,4561,4562,4570,4596,4603],{},[31,4563,4266,4564,4566,4567,494],{},[41,4565,4312],{}," - migracja do ",[41,4568,4569],{},"Pinia Store",[417,4571,4572,4578,4584,4593],{},[420,4573,4574,4575],{},"strona ma wiele elementów, które potrzebują danych z ",[41,4576,4577],{},"Supabase REST API",[420,4579,4580,4581,4583],{},"wszystkie dane potrzebne na tej stronie, łącznie z zagnieżdżonymi komponentami, mają korzystać z globalnego ",[41,4582,4569],{}," reprezentującego dane lekarza",[420,4585,4586,4587,4589,4590],{},"wszystkie wywołania ",[41,4588,4577],{}," mają znaleźć się w akcjach ",[41,4591,4592],{},"store",[420,4594,4595],{},"wykonanie akcji takich jak zakończenie wizyty, odwołanie wizyty, usunięcie dostępności, ustalenie dostępności powinny inicjować odświeżenie tabeli historii zdarzeń lekarza",[31,4597,4266,4598,4566,4601,494],{},[41,4599,4600],{},"\u002Fpacjenci\u002F:id",[41,4602,4569],{},[417,4604,4605,4613],{},[420,4606,4607,4608,4610,4611],{},"podobnie jak dla lekarzy, migruj do ",[41,4609,4569],{}," obsługę stanu i komunikacji z ",[41,4612,4577],{},[420,4614,4615],{},"akcje takie jak zakończenie wizyty, odwołanie wizyty, powinny inicjować odświeżenie tabeli historii zdarzeń pacjenta",[864,4617,4619],{"id":4618},"przykłady-prompt-ów-korygujących-agenta-czyli-strofowanie-asystenta","Przykłady prompt-ów korygujących agenta, czyli strofowanie asystenta:",[4174,4621,4622,4629,4635,4641,4644],{},[31,4623,4624,4625,4628],{},"Sprawdź komponent ",[41,4626,4627],{},"UPagination",", stronicowanie dalej nie działa.",[31,4630,4631,4632,56],{},"Nie działa wywołanie okna dialogowego, nie użyto ",[41,4633,4634],{},"useOverlay",[31,4636,4637,4638,56],{},"Okno dialogowe otwiera się, ale nic się nie pokazuje. Sprawdź dokumentację ",[41,4639,4640],{},"UModal",[31,4642,4643],{},"Lista rozwijana lekarzy rodzinnych nie pokazuje wartości do wyboru.",[31,4645,4646,4647,4650,4651,4654,4655,56],{},"Nie podoba mi się rozwiązanie z natywnym ",[41,4648,4649],{},"form",". Użyj ",[41,4652,4653],{},"UForm",", który umożliwia integracje ze schematem walidacji ",[41,4656,4657],{},"zod",[26,4659,4661],{"id":4660},"materiał-wideo","Materiał wideo",[31,4663,4664],{},"Nagranie demonstrujące co potrafi 2-dniowy prototyp.",[4666,4667,4669,4670,4675],"video",{"controls":10,"width":4668},800,"\n  ",[4671,4672],"source",{"src":4673,"type":4674},"https:\u002F\u002Fdr.awfs.dev\u002Fvideos\u002Fproto-app-001.mp4","video\u002Fmp4","\n  Twoja przeglądarka nie wspiera odtwarzania wideo.\n",[2065,4677,4678],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":136,"searchDepth":185,"depth":185,"links":4680},[4681,4682,4683,4684,4685,4695],{"id":3692,"depth":185,"text":3693},{"id":3719,"depth":185,"text":3720},{"id":3769,"depth":185,"text":3770},{"id":4087,"depth":185,"text":4088},{"id":4129,"depth":185,"text":4130,"children":4686},[4687,4688,4689,4690,4691,4692,4693,4694],{"id":4171,"depth":216,"text":4172},{"id":4260,"depth":216,"text":4261},{"id":4303,"depth":216,"text":4304},{"id":4352,"depth":216,"text":4353},{"id":4377,"depth":216,"text":4378},{"id":4439,"depth":216,"text":4440},{"id":4558,"depth":216,"text":4559},{"id":4618,"depth":216,"text":4619},{"id":4660,"depth":185,"text":4661},"2025-11-17","W dwa dni robocze, z pomocą Copilot-a i MCP, zbudowałem działającą makietę aplikacji medycznej.","\u002Fpics\u002F0003.png",{"order":216},"\u002Fblog\u002Fmakieta-nuxt-supabase",{"title":3687,"description":4697},{"loc":4700},"blog\u002Fmakieta-nuxt-supabase","K_4whd6HA7Wb9x6BDFE-jzVwYxPFhBO0vvMmhk-bZ08",{"id":4706,"title":4707,"author":21,"body":4708,"date":9213,"description":9214,"draft":2088,"extension":2089,"image":9215,"meta":9216,"minRead":6,"navigation":10,"path":9217,"seo":9218,"sitemap":9219,"stem":9220,"__hash__":9221},"blog\u002Fblog\u002Fai-for-coding.md","AI for coding? Nie ma ucieczki!",{"type":23,"value":4709,"toc":9207},[4710,4712,4732,4741,4765,4772,4776,4786,4792,4803,4858,4862,4865,4874,4993,5000,5110,5117,7049,7055,7058,7070,7088,9132,9134,9147,9153,9162,9180,9194,9197,9204],[26,4711,29],{"id":28},[31,4713,4714,4715,4720,4721,64,4724,4727,4728,4731],{},"Od pewnego czasu używam ",[41,4716,4717],{},[35,4718,3783],{"href":2114,"rel":4719},[111]," i modeli takich jak ",[41,4722,4723],{},"GPT-5",[41,4725,4726],{},"Gemini 2.5 Pro"," oraz ",[41,4729,4730],{},"Claude Sonnet 4.5"," do testowego tworzenia kodu.",[31,4733,4734,4735,4740],{},"Używam rozszerzenia do ",[41,4736,4737],{},[35,4738,2124],{"href":2122,"rel":4739},[111],", które daje możliwość pracy w trzech trybach:",[417,4742,4743,4749,4755],{},[420,4744,4745,4748],{},[41,4746,4747],{},"Ask",", jeśli chcesz o coś zapytać bez automatycznej ingerencji w istniejący kod",[420,4750,4751,4754],{},[41,4752,4753],{},"Edit",", jeśli chcesz wykonać konkretną, najczęściej niewielką, zmianę w kodzie",[420,4756,4757,4760,4761,4764],{},[41,4758,4759],{},"Agent",", jeśli chcesz, żeby ",[41,4762,4763],{},"Copilot"," był bardziej samodzielny, analizował istniejący kod i wykonywał większe modyfikacje zgodnie z Twoimi wytycznymi",[31,4766,4767,4768,4771],{},"Domyślnie włączone jest też automatyczne uzupełnianie kodu (",[41,4769,4770],{},"Code Completions","), które cały czas próbuje podpowiadać kolejne instrukcje, frazy czy całe bloki kodu — można je uśpić lub wyłączyć, jeśli przeszkadza. Czasami bywa to irytujące, a czasami przerażające, bo wydaje się, że agent czyta w myślach i proponuje dokładnie to (i w takiej formie), o czym akurat pomyślałem.",[26,4773,4775],{"id":4774},"zadanie-dla-agenta","Zadanie dla agenta",[31,4777,4778,4779,4782,4783,4785],{},"W jednym z ostatnio realizowanych projektów pojawiła się potrzeba szybkiego napisania klasy obsługującej skrzynkę pocztową ",[41,4780,4781],{},"MS Exchange",". W nowym, pustym folderze, w trybie ",[41,4784,4759],{}," piszę taki prompt:",[131,4787,4790],{"className":4788,"code":4789,"language":891},[889],"Zainicjuj nowy projekt **Node.js**, a następnie przygotuj klasę **ExchangeMailbox**:\n- nowa klasa w dedykowanym pliku **ExchangeMailbox.js**\n- jedna metoda **sendMail**, która daje możliwość wysyłania wiadomości pocztowych\n- użyj **MS Graph API**\n- zainstaluj wszystkie potrzebne pakiety **NPM**\n- wszystkie potrzebne dane dostępowe do API, np. **Tenant ID**, pobieraj ze zmiennych środowiskowych definiowanych w pliku **.env**\n- utwórz plik **main.js** i zaimplementuj testy nowej klasy\n",[61,4791,4789],{"__ignoreMap":136},[31,4793,4794,4795,4802],{},"Prompt redaguję z użyciem składni ",[41,4796,4797],{},[35,4798,4801],{"href":4799,"rel":4800},"https:\u002F\u002Fpl.wikipedia.org\u002Fwiki\u002FMarkdown",[111],"Markdown",", ale nie mam pewności, czy dla agenta ma znaczenie, że słowa kluczowe są w ten sposób wyróżnione. Tak czy owak, prompt w oknie pracy agenta jest czytelniejszy i prezentuje się tak:",[4174,4804,4805,4815],{},[31,4806,4807,4808,4811,4812,494],{},"Zainicjuj nowy projekt ",[41,4809,4810],{},"Node.js",", a następnie przygotuj klasę ",[41,4813,4814],{},"ExchangeMailbox",[417,4816,4817,4823,4830,4836,4842,4851],{},[420,4818,4819,4820],{},"nowa klasa w dedykowanym pliku ",[41,4821,4822],{},"ExchangeMailbox.js",[420,4824,4825,4826,4829],{},"jedna metoda ",[41,4827,4828],{},"sendMail",", która daje możliwość wysyłania wiadomości pocztowych",[420,4831,4832,4833],{},"użyj ",[41,4834,4835],{},"MS Graph API",[420,4837,4838,4839],{},"zainstaluj wszystkie potrzebne pakiety ",[41,4840,4841],{},"NPM",[420,4843,4844,4845,4848,4849],{},"wszystkie potrzebne dane dostępowe do API, np. ",[41,4846,4847],{},"Tenant ID",", pobieraj ze zmiennych środowiskowych definiowanych w pliku ",[41,4850,783],{},[420,4852,4853,4854,4857],{},"utwórz plik ",[41,4855,4856],{},"main.js"," i zaimplementuj testy nowej klasy",[26,4859,4861],{"id":4860},"wyniki-pracy-agenta","Wyniki pracy agenta",[31,4863,4864],{},"Agent pracował około 3 minut i przygotował:",[417,4866,4867],{},[420,4868,4869,4870,4873],{},"plik ",[41,4871,4872],{},"copilot-instructions.md",", który jest lepszą wersją mojego prompt-a",[131,4875,4878],{"className":4876,"code":4877,"filename":4872,"language":2089,"meta":136,"style":136},"language-md shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# Node.js ExchangeMailbox Project\n\nThis project implements a Node.js application with ExchangeMailbox class for sending emails via MS Graph API.\n\n## Features\n\n- ExchangeMailbox class with sendMail method\n- MS Graph API integration for email sending\n- Environment variable configuration\n- Sample test implementation\n\n## Setup\n\n1. Install dependencies: `npm install`\n2. Configure .env file with MS Graph API credentials\n3. Run test: `node main.js`\n",[61,4879,4880,4888,4892,4897,4901,4909,4913,4920,4927,4934,4941,4945,4952,4956,4971,4978],{"__ignoreMap":136},[140,4881,4882,4885],{"class":142,"line":143},[140,4883,4884],{"class":146},"# ",[140,4886,4887],{"class":363},"Node.js ExchangeMailbox Project\n",[140,4889,4890],{"class":142,"line":185},[140,4891,574],{"emptyLinePlaceholder":10},[140,4893,4894],{"class":142,"line":216},[140,4895,4896],{"class":556},"This project implements a Node.js application with ExchangeMailbox class for sending emails via MS Graph API.\n",[140,4898,4899],{"class":142,"line":247},[140,4900,574],{"emptyLinePlaceholder":10},[140,4902,4903,4906],{"class":142,"line":616},[140,4904,4905],{"class":146},"## ",[140,4907,4908],{"class":363},"Features\n",[140,4910,4911],{"class":142,"line":624},[140,4912,574],{"emptyLinePlaceholder":10},[140,4914,4915,4917],{"class":142,"line":641},[140,4916,579],{"class":146},[140,4918,4919],{"class":556}," ExchangeMailbox class with sendMail method\n",[140,4921,4922,4924],{"class":142,"line":649},[140,4923,579],{"class":146},[140,4925,4926],{"class":556}," MS Graph API integration for email sending\n",[140,4928,4929,4931],{"class":142,"line":657},[140,4930,579],{"class":146},[140,4932,4933],{"class":556}," Environment variable configuration\n",[140,4935,4936,4938],{"class":142,"line":665},[140,4937,579],{"class":146},[140,4939,4940],{"class":556}," Sample test implementation\n",[140,4942,4943],{"class":142,"line":704},[140,4944,574],{"emptyLinePlaceholder":10},[140,4946,4947,4949],{"class":142,"line":730},[140,4948,4905],{"class":146},[140,4950,4951],{"class":363},"Setup\n",[140,4953,4954],{"class":142,"line":738},[140,4955,574],{"emptyLinePlaceholder":10},[140,4957,4958,4960,4963,4965,4968],{"class":142,"line":756},[140,4959,1263],{"class":146},[140,4961,4962],{"class":556}," Install dependencies: ",[140,4964,598],{"class":146},[140,4966,4967],{"class":164},"npm install",[140,4969,4970],{"class":146},"`\n",[140,4972,4973,4975],{"class":142,"line":773},[140,4974,1280],{"class":146},[140,4976,4977],{"class":556}," Configure .env file with MS Graph API credentials\n",[140,4979,4980,4983,4986,4988,4991],{"class":142,"line":790},[140,4981,4982],{"class":146},"3.",[140,4984,4985],{"class":556}," Run test: ",[140,4987,598],{"class":146},[140,4989,4990],{"class":164},"node main.js",[140,4992,4970],{"class":146},[417,4994,4995],{},[420,4996,4869,4997,4999],{},[41,4998,783],{}," z komentarzami",[131,5001,5003],{"className":354,"code":5002,"filename":783,"language":356,"meta":136,"style":136},"# MS Graph API Configuration\n# Uzyskaj te wartości z Azure Portal -> App registrations\n\n# ID aplikacji (Client ID) z Azure AD\nAZURE_CLIENT_ID=your_client_id_here\n\n# Klucz tajny aplikacji (Client Secret) z Azure AD\nAZURE_CLIENT_SECRET=your_client_secret_here\n\n# ID dzierżawy (Tenant ID) z Azure AD\nAZURE_TENANT_ID=your_tenant_id_here\n\n# Email nadawcy (użytkownik, którego konto będzie używane do wysyłania)\nSENDER_EMAIL=your_sender_email@yourdomain.com\n\n# Opcjonalne: Email odbiorcy dla testów\nTEST_RECIPIENT_EMAIL=test_recipient@example.com\n",[61,5004,5005,5010,5015,5019,5024,5034,5038,5043,5053,5057,5062,5072,5076,5081,5091,5095,5100],{"__ignoreMap":136},[140,5006,5007],{"class":142,"line":143},[140,5008,5009],{"class":1650},"# MS Graph API Configuration\n",[140,5011,5012],{"class":142,"line":185},[140,5013,5014],{"class":1650},"# Uzyskaj te wartości z Azure Portal -> App registrations\n",[140,5016,5017],{"class":142,"line":216},[140,5018,574],{"emptyLinePlaceholder":10},[140,5020,5021],{"class":142,"line":247},[140,5022,5023],{"class":1650},"# ID aplikacji (Client ID) z Azure AD\n",[140,5025,5026,5029,5031],{"class":142,"line":616},[140,5027,5028],{"class":556},"AZURE_CLIENT_ID",[140,5030,158],{"class":146},[140,5032,5033],{"class":164},"your_client_id_here\n",[140,5035,5036],{"class":142,"line":624},[140,5037,574],{"emptyLinePlaceholder":10},[140,5039,5040],{"class":142,"line":641},[140,5041,5042],{"class":1650},"# Klucz tajny aplikacji (Client Secret) z Azure AD\n",[140,5044,5045,5048,5050],{"class":142,"line":649},[140,5046,5047],{"class":556},"AZURE_CLIENT_SECRET",[140,5049,158],{"class":146},[140,5051,5052],{"class":164},"your_client_secret_here\n",[140,5054,5055],{"class":142,"line":657},[140,5056,574],{"emptyLinePlaceholder":10},[140,5058,5059],{"class":142,"line":665},[140,5060,5061],{"class":1650},"# ID dzierżawy (Tenant ID) z Azure AD\n",[140,5063,5064,5067,5069],{"class":142,"line":704},[140,5065,5066],{"class":556},"AZURE_TENANT_ID",[140,5068,158],{"class":146},[140,5070,5071],{"class":164},"your_tenant_id_here\n",[140,5073,5074],{"class":142,"line":730},[140,5075,574],{"emptyLinePlaceholder":10},[140,5077,5078],{"class":142,"line":738},[140,5079,5080],{"class":1650},"# Email nadawcy (użytkownik, którego konto będzie używane do wysyłania)\n",[140,5082,5083,5086,5088],{"class":142,"line":756},[140,5084,5085],{"class":556},"SENDER_EMAIL",[140,5087,158],{"class":146},[140,5089,5090],{"class":164},"your_sender_email@yourdomain.com\n",[140,5092,5093],{"class":142,"line":773},[140,5094,574],{"emptyLinePlaceholder":10},[140,5096,5097],{"class":142,"line":790},[140,5098,5099],{"class":1650},"# Opcjonalne: Email odbiorcy dla testów\n",[140,5101,5102,5105,5107],{"class":142,"line":1409},[140,5103,5104],{"class":556},"TEST_RECIPIENT_EMAIL",[140,5106,158],{"class":146},[140,5108,5109],{"class":164},"test_recipient@example.com\n",[417,5111,5112],{},[420,5113,5114,5115],{},"główny plik implementujący klasę - ",[41,5116,4822],{},[5118,5119,5120],"code-collapse",{},[131,5121,5123],{"className":1018,"code":5122,"filename":4822,"language":1020,"meta":136,"style":136},"import { ConfidentialClientApplication } from '@azure\u002Fmsal-node'\nimport { Client } from '@microsoft\u002Fmicrosoft-graph-client'\nimport dotenv from 'dotenv'\n\n\u002F\u002F Ładowanie zmiennych środowiskowych\ndotenv.config()\n\n\u002F**\n * Klasa do uwierzytelniania z MS Graph API\n *\u002F\nclass MSALAuthProvider {\n  constructor() {\n    this.clientApp = new ConfidentialClientApplication({\n      auth: {\n        clientId: process.env.AZURE_CLIENT_ID,\n        clientSecret: process.env.AZURE_CLIENT_SECRET,\n        authority: `https:\u002F\u002Flogin.microsoftonline.com\u002F${process.env.AZURE_TENANT_ID}`\n      }\n    })\n  }\n\n  async getAccessToken() {\n    const request = {\n      scopes: ['https:\u002F\u002Fgraph.microsoft.com\u002F.default']\n    }\n\n    try {\n      const response = await this.clientApp.acquireTokenByClientCredential(request)\n      return response.accessToken\n    } catch (error) {\n      console.error('Error acquiring token:', error)\n      throw error\n    }\n  }\n}\n\n\u002F**\n * Klasa ExchangeMailbox do wysyłania wiadomości email przez MS Graph API\n *\u002F\nexport class ExchangeMailbox {\n  constructor() {\n    this.authProvider = new MSALAuthProvider()\n    this.client = null\n    this.userEmail = process.env.SENDER_EMAIL\n  }\n\n  \u002F**\n   * Inicjalizacja klienta Graph API\n   *\u002F\n  async initialize() {\n    try {\n      this.client = Client.init({\n        authProvider: async done => {\n          try {\n            const accessToken = await this.authProvider.getAccessToken()\n            done(null, accessToken)\n          } catch (error) {\n            done(error, null)\n          }\n        }\n      })\n    } catch (error) {\n      console.error('Error initializing Graph client:', error)\n      throw error\n    }\n  }\n\n  \u002F**\n   * Wysyła wiadomość email\n   * @param {Object} mailOptions - Opcje wiadomości\n   * @param {string} mailOptions.to - Adres odbiorcy\n   * @param {string} mailOptions.subject - Temat wiadomości\n   * @param {string} mailOptions.body - Treść wiadomości (HTML lub tekst)\n   * @param {string} [mailOptions.bodyType='HTML'] - Typ treści: 'HTML' lub 'Text'\n   * @param {Array} [mailOptions.attachments] - Załączniki (opcjonalne)\n   * @returns {Promise\u003CObject>} Wynik wysyłania\n   *\u002F\n  async sendMail(mailOptions) {\n    if (!this.client) {\n      await this.initialize()\n    }\n\n    const { to, subject, body, bodyType = 'HTML', attachments = [] } = mailOptions\n\n    if (!to || !subject || !body) {\n      throw new Error('Parametry to, subject i body są wymagane')\n    }\n\n    const message = {\n      subject: subject,\n      body: {\n        contentType: bodyType,\n        content: body\n      },\n      toRecipients: [\n        {\n          emailAddress: {\n            address: to\n          }\n        }\n      ]\n    }\n\n    \u002F\u002F Dodanie załączników jeśli są dostępne\n    if (attachments && attachments.length > 0) {\n      message.attachments = attachments.map(attachment => ({\n        '@odata.type': '#microsoft.graph.fileAttachment',\n        'name': attachment.name,\n        'contentBytes': attachment.contentBytes, \u002F\u002F Base64 encoded\n        'contentType': attachment.contentType || 'application\u002Foctet-stream'\n      }))\n    }\n\n    try {\n      const response = await this.client.api(`\u002Fusers\u002F${this.userEmail}\u002FsendMail`).post({\n        message: message\n      })\n\n      console.log(`Email wysłany pomyślnie do: ${to}`)\n      return {\n        success: true,\n        message: 'Email wysłany pomyślnie',\n        response: response\n      }\n    } catch (error) {\n      console.error('Błąd podczas wysyłania emaila:', error)\n      return {\n        success: false,\n        message: 'Błąd podczas wysyłania emaila',\n        error: error.message\n      }\n    }\n  }\n\n  \u002F**\n   * Sprawdza połączenie z MS Graph API\n   * @returns {Promise\u003Cboolean>} True jeśli połączenie działa\n   *\u002F\n  async testConnection() {\n    try {\n      if (!this.client) {\n        await this.initialize()\n      }\n\n      \u002F\u002F Testujemy połączenie sprawdzając token dostępu\n      const accessToken = await this.authProvider.getAccessToken()\n      if (accessToken && accessToken.length > 0) {\n        console.log('Połączenie z MS Graph API działa. Token otrzymany pomyślnie.')\n        return true\n      } else {\n        console.log('Nie udało się uzyskać tokenu dostępu.')\n        return false\n      }\n    } catch (error) {\n      console.error('Błąd połączenia z MS Graph API:', error)\n      return false\n    }\n  }\n}\n",[61,5124,5125,5147,5167,5184,5188,5193,5204,5208,5213,5218,5223,5233,5242,5262,5271,5292,5311,5341,5346,5353,5357,5361,5373,5385,5404,5408,5412,5419,5448,5460,5477,5502,5510,5514,5518,5522,5526,5530,5535,5539,5552,5560,5575,5587,5607,5611,5615,5620,5625,5630,5641,5647,5667,5686,5693,5716,5730,5745,5760,5765,5770,5777,5791,5815,5822,5827,5832,5837,5842,5848,5875,5897,5918,5939,5970,5996,6016,6021,6039,6056,6069,6074,6079,6130,6135,6168,6189,6194,6199,6211,6223,6233,6245,6256,6262,6272,6278,6288,6299,6304,6309,6315,6320,6325,6331,6360,6390,6412,6433,6456,6483,6490,6495,6500,6507,6559,6570,6577,6582,6608,6615,6629,6645,6656,6661,6676,6700,6707,6719,6735,6750,6755,6760,6765,6770,6775,6781,6800,6805,6817,6824,6840,6852,6857,6862,6868,6889,6915,6936,6945,6955,6975,6983,6988,7003,7027,7034,7039,7044],{"__ignoreMap":136},[140,5126,5127,5130,5132,5135,5137,5140,5142,5145],{"class":142,"line":143},[140,5128,5129],{"class":585},"import",[140,5131,1068],{"class":146},[140,5133,5134],{"class":556}," ConfidentialClientApplication",[140,5136,1098],{"class":146},[140,5138,5139],{"class":585}," from",[140,5141,382],{"class":146},[140,5143,5144],{"class":164},"@azure\u002Fmsal-node",[140,5146,388],{"class":146},[140,5148,5149,5151,5153,5156,5158,5160,5162,5165],{"class":142,"line":185},[140,5150,5129],{"class":585},[140,5152,1068],{"class":146},[140,5154,5155],{"class":556}," Client",[140,5157,1098],{"class":146},[140,5159,5139],{"class":585},[140,5161,382],{"class":146},[140,5163,5164],{"class":164},"@microsoft\u002Fmicrosoft-graph-client",[140,5166,388],{"class":146},[140,5168,5169,5171,5174,5177,5179,5182],{"class":142,"line":216},[140,5170,5129],{"class":585},[140,5172,5173],{"class":556}," dotenv ",[140,5175,5176],{"class":585},"from",[140,5178,382],{"class":146},[140,5180,5181],{"class":164},"dotenv",[140,5183,388],{"class":146},[140,5185,5186],{"class":142,"line":247},[140,5187,574],{"emptyLinePlaceholder":10},[140,5189,5190],{"class":142,"line":616},[140,5191,5192],{"class":1650},"\u002F\u002F Ładowanie zmiennych środowiskowych\n",[140,5194,5195,5197,5199,5202],{"class":142,"line":624},[140,5196,5181],{"class":556},[140,5198,56],{"class":146},[140,5200,5201],{"class":1038},"config",[140,5203,1111],{"class":556},[140,5205,5206],{"class":142,"line":641},[140,5207,574],{"emptyLinePlaceholder":10},[140,5209,5210],{"class":142,"line":649},[140,5211,5212],{"class":1650},"\u002F**\n",[140,5214,5215],{"class":142,"line":657},[140,5216,5217],{"class":1650}," * Klasa do uwierzytelniania z MS Graph API\n",[140,5219,5220],{"class":142,"line":665},[140,5221,5222],{"class":1650}," *\u002F\n",[140,5224,5225,5228,5231],{"class":142,"line":704},[140,5226,5227],{"class":154},"class",[140,5229,5230],{"class":363}," MSALAuthProvider",[140,5232,3902],{"class":146},[140,5234,5235,5238,5240],{"class":142,"line":730},[140,5236,5237],{"class":154},"  constructor",[140,5239,1628],{"class":146},[140,5241,3902],{"class":146},[140,5243,5244,5247,5250,5253,5256,5258,5260],{"class":142,"line":738},[140,5245,5246],{"class":146},"    this.",[140,5248,5249],{"class":556},"clientApp",[140,5251,5252],{"class":146}," =",[140,5254,5255],{"class":146}," new",[140,5257,5134],{"class":1038},[140,5259,1053],{"class":150},[140,5261,3887],{"class":146},[140,5263,5264,5267,5269],{"class":142,"line":756},[140,5265,5266],{"class":150},"      auth",[140,5268,494],{"class":146},[140,5270,3902],{"class":146},[140,5272,5273,5276,5278,5281,5283,5286,5288,5290],{"class":142,"line":773},[140,5274,5275],{"class":150},"        clientId",[140,5277,494],{"class":146},[140,5279,5280],{"class":556}," process",[140,5282,56],{"class":146},[140,5284,5285],{"class":556},"env",[140,5287,56],{"class":146},[140,5289,5028],{"class":556},[140,5291,3938],{"class":146},[140,5293,5294,5297,5299,5301,5303,5305,5307,5309],{"class":142,"line":790},[140,5295,5296],{"class":150},"        clientSecret",[140,5298,494],{"class":146},[140,5300,5280],{"class":556},[140,5302,56],{"class":146},[140,5304,5285],{"class":556},[140,5306,56],{"class":146},[140,5308,5047],{"class":556},[140,5310,3938],{"class":146},[140,5312,5313,5316,5318,5321,5324,5327,5330,5332,5334,5336,5338],{"class":142,"line":1409},[140,5314,5315],{"class":150},"        authority",[140,5317,494],{"class":146},[140,5319,5320],{"class":146}," `",[140,5322,5323],{"class":164},"https:\u002F\u002Flogin.microsoftonline.com\u002F",[140,5325,5326],{"class":146},"${",[140,5328,5329],{"class":556},"process",[140,5331,56],{"class":146},[140,5333,5285],{"class":556},[140,5335,56],{"class":146},[140,5337,5066],{"class":556},[140,5339,5340],{"class":146},"}`\n",[140,5342,5343],{"class":142,"line":1424},[140,5344,5345],{"class":146},"      }\n",[140,5347,5348,5351],{"class":142,"line":1440},[140,5349,5350],{"class":146},"    }",[140,5352,1101],{"class":150},[140,5354,5355],{"class":142,"line":1455},[140,5356,4079],{"class":146},[140,5358,5359],{"class":142,"line":1510},[140,5360,574],{"emptyLinePlaceholder":10},[140,5362,5363,5366,5369,5371],{"class":142,"line":2356},[140,5364,5365],{"class":154},"  async",[140,5367,5368],{"class":150}," getAccessToken",[140,5370,1628],{"class":146},[140,5372,3902],{"class":146},[140,5374,5375,5378,5381,5383],{"class":142,"line":2362},[140,5376,5377],{"class":154},"    const",[140,5379,5380],{"class":556}," request",[140,5382,5252],{"class":146},[140,5384,3902],{"class":146},[140,5386,5387,5390,5392,5395,5397,5400,5402],{"class":142,"line":2367},[140,5388,5389],{"class":150},"      scopes",[140,5391,494],{"class":146},[140,5393,5394],{"class":150}," [",[140,5396,1081],{"class":146},[140,5398,5399],{"class":164},"https:\u002F\u002Fgraph.microsoft.com\u002F.default",[140,5401,1081],{"class":146},[140,5403,1701],{"class":150},[140,5405,5406],{"class":142,"line":2373},[140,5407,4074],{"class":146},[140,5409,5410],{"class":142,"line":2378},[140,5411,574],{"emptyLinePlaceholder":10},[140,5413,5414,5417],{"class":142,"line":2384},[140,5415,5416],{"class":585},"    try",[140,5418,3902],{"class":146},[140,5420,5421,5424,5427,5429,5431,5434,5436,5438,5441,5443,5446],{"class":142,"line":2390},[140,5422,5423],{"class":154},"      const",[140,5425,5426],{"class":556}," response",[140,5428,5252],{"class":146},[140,5430,1035],{"class":585},[140,5432,5433],{"class":146}," this.",[140,5435,5249],{"class":556},[140,5437,56],{"class":146},[140,5439,5440],{"class":1038},"acquireTokenByClientCredential",[140,5442,1053],{"class":150},[140,5444,5445],{"class":556},"request",[140,5447,1101],{"class":150},[140,5449,5450,5453,5455,5457],{"class":142,"line":2396},[140,5451,5452],{"class":585},"      return",[140,5454,5426],{"class":556},[140,5456,56],{"class":146},[140,5458,5459],{"class":556},"accessToken\n",[140,5461,5462,5464,5467,5469,5472,5475],{"class":142,"line":2402},[140,5463,5350],{"class":146},[140,5465,5466],{"class":585}," catch",[140,5468,303],{"class":150},[140,5470,5471],{"class":556},"error",[140,5473,5474],{"class":150},") ",[140,5476,3887],{"class":146},[140,5478,5479,5482,5484,5486,5488,5490,5493,5495,5497,5500],{"class":142,"line":2408},[140,5480,5481],{"class":556},"      console",[140,5483,56],{"class":146},[140,5485,5471],{"class":1038},[140,5487,1053],{"class":150},[140,5489,1081],{"class":146},[140,5491,5492],{"class":164},"Error acquiring token:",[140,5494,1081],{"class":146},[140,5496,1060],{"class":146},[140,5498,5499],{"class":556}," error",[140,5501,1101],{"class":150},[140,5503,5504,5507],{"class":142,"line":2414},[140,5505,5506],{"class":585},"      throw",[140,5508,5509],{"class":556}," error\n",[140,5511,5512],{"class":142,"line":2419},[140,5513,4074],{"class":146},[140,5515,5516],{"class":142,"line":2425},[140,5517,4079],{"class":146},[140,5519,5520],{"class":142,"line":2430},[140,5521,4084],{"class":146},[140,5523,5524],{"class":142,"line":2436},[140,5525,574],{"emptyLinePlaceholder":10},[140,5527,5528],{"class":142,"line":2442},[140,5529,5212],{"class":1650},[140,5531,5532],{"class":142,"line":2448},[140,5533,5534],{"class":1650}," * Klasa ExchangeMailbox do wysyłania wiadomości email przez MS Graph API\n",[140,5536,5537],{"class":142,"line":2454},[140,5538,5222],{"class":1650},[140,5540,5541,5544,5547,5550],{"class":142,"line":2460},[140,5542,5543],{"class":585},"export",[140,5545,5546],{"class":154}," class",[140,5548,5549],{"class":363}," ExchangeMailbox",[140,5551,3902],{"class":146},[140,5553,5554,5556,5558],{"class":142,"line":2466},[140,5555,5237],{"class":154},[140,5557,1628],{"class":146},[140,5559,3902],{"class":146},[140,5561,5562,5564,5567,5569,5571,5573],{"class":142,"line":2472},[140,5563,5246],{"class":146},[140,5565,5566],{"class":556},"authProvider",[140,5568,5252],{"class":146},[140,5570,5255],{"class":146},[140,5572,5230],{"class":1038},[140,5574,1111],{"class":150},[140,5576,5577,5579,5582,5584],{"class":142,"line":2477},[140,5578,5246],{"class":146},[140,5580,5581],{"class":556},"client",[140,5583,5252],{"class":146},[140,5585,5586],{"class":146}," null\n",[140,5588,5589,5591,5594,5596,5598,5600,5602,5604],{"class":142,"line":2483},[140,5590,5246],{"class":146},[140,5592,5593],{"class":556},"userEmail",[140,5595,5252],{"class":146},[140,5597,5280],{"class":556},[140,5599,56],{"class":146},[140,5601,5285],{"class":556},[140,5603,56],{"class":146},[140,5605,5606],{"class":556},"SENDER_EMAIL\n",[140,5608,5609],{"class":142,"line":2489},[140,5610,4079],{"class":146},[140,5612,5613],{"class":142,"line":2495},[140,5614,574],{"emptyLinePlaceholder":10},[140,5616,5617],{"class":142,"line":2501},[140,5618,5619],{"class":1650},"  \u002F**\n",[140,5621,5622],{"class":142,"line":2507},[140,5623,5624],{"class":1650},"   * Inicjalizacja klienta Graph API\n",[140,5626,5627],{"class":142,"line":2513},[140,5628,5629],{"class":1650},"   *\u002F\n",[140,5631,5632,5634,5637,5639],{"class":142,"line":2519},[140,5633,5365],{"class":154},[140,5635,5636],{"class":150}," initialize",[140,5638,1628],{"class":146},[140,5640,3902],{"class":146},[140,5642,5643,5645],{"class":142,"line":2525},[140,5644,5416],{"class":585},[140,5646,3902],{"class":146},[140,5648,5649,5652,5654,5656,5658,5660,5663,5665],{"class":142,"line":2531},[140,5650,5651],{"class":146},"      this.",[140,5653,5581],{"class":556},[140,5655,5252],{"class":146},[140,5657,5155],{"class":556},[140,5659,56],{"class":146},[140,5661,5662],{"class":1038},"init",[140,5664,1053],{"class":150},[140,5666,3887],{"class":146},[140,5668,5669,5672,5674,5677,5681,5684],{"class":142,"line":2537},[140,5670,5671],{"class":1038},"        authProvider",[140,5673,494],{"class":146},[140,5675,5676],{"class":154}," async",[140,5678,5680],{"class":5679},"sHdIc"," done",[140,5682,5683],{"class":154}," =>",[140,5685,3902],{"class":146},[140,5687,5688,5691],{"class":142,"line":2543},[140,5689,5690],{"class":585},"          try",[140,5692,3902],{"class":146},[140,5694,5695,5698,5701,5703,5705,5707,5709,5711,5714],{"class":142,"line":2548},[140,5696,5697],{"class":154},"            const",[140,5699,5700],{"class":556}," accessToken",[140,5702,5252],{"class":146},[140,5704,1035],{"class":585},[140,5706,5433],{"class":146},[140,5708,5566],{"class":556},[140,5710,56],{"class":146},[140,5712,5713],{"class":1038},"getAccessToken",[140,5715,1111],{"class":150},[140,5717,5718,5721,5723,5726,5728],{"class":142,"line":2554},[140,5719,5720],{"class":1038},"            done",[140,5722,1053],{"class":150},[140,5724,5725],{"class":146},"null,",[140,5727,5700],{"class":556},[140,5729,1101],{"class":150},[140,5731,5732,5735,5737,5739,5741,5743],{"class":142,"line":2559},[140,5733,5734],{"class":146},"          }",[140,5736,5466],{"class":585},[140,5738,303],{"class":150},[140,5740,5471],{"class":556},[140,5742,5474],{"class":150},[140,5744,3887],{"class":146},[140,5746,5747,5749,5751,5753,5755,5758],{"class":142,"line":2565},[140,5748,5720],{"class":1038},[140,5750,1053],{"class":150},[140,5752,5471],{"class":556},[140,5754,1060],{"class":146},[140,5756,5757],{"class":146}," null",[140,5759,1101],{"class":150},[140,5761,5762],{"class":142,"line":2571},[140,5763,5764],{"class":146},"          }\n",[140,5766,5767],{"class":142,"line":2576},[140,5768,5769],{"class":146},"        }\n",[140,5771,5772,5775],{"class":142,"line":2582},[140,5773,5774],{"class":146},"      }",[140,5776,1101],{"class":150},[140,5778,5779,5781,5783,5785,5787,5789],{"class":142,"line":2587},[140,5780,5350],{"class":146},[140,5782,5466],{"class":585},[140,5784,303],{"class":150},[140,5786,5471],{"class":556},[140,5788,5474],{"class":150},[140,5790,3887],{"class":146},[140,5792,5794,5796,5798,5800,5802,5804,5807,5809,5811,5813],{"class":142,"line":5793},63,[140,5795,5481],{"class":556},[140,5797,56],{"class":146},[140,5799,5471],{"class":1038},[140,5801,1053],{"class":150},[140,5803,1081],{"class":146},[140,5805,5806],{"class":164},"Error initializing Graph client:",[140,5808,1081],{"class":146},[140,5810,1060],{"class":146},[140,5812,5499],{"class":556},[140,5814,1101],{"class":150},[140,5816,5818,5820],{"class":142,"line":5817},64,[140,5819,5506],{"class":585},[140,5821,5509],{"class":556},[140,5823,5825],{"class":142,"line":5824},65,[140,5826,4074],{"class":146},[140,5828,5830],{"class":142,"line":5829},66,[140,5831,4079],{"class":146},[140,5833,5835],{"class":142,"line":5834},67,[140,5836,574],{"emptyLinePlaceholder":10},[140,5838,5840],{"class":142,"line":5839},68,[140,5841,5619],{"class":1650},[140,5843,5845],{"class":142,"line":5844},69,[140,5846,5847],{"class":1650},"   * Wysyła wiadomość email\n",[140,5849,5851,5854,5857,5861,5863,5867,5869,5872],{"class":142,"line":5850},70,[140,5852,5853],{"class":1650},"   * ",[140,5855,5856],{"class":585},"@",[140,5858,5860],{"class":5859},"s6hCs","param",[140,5862,1068],{"class":585},[140,5864,5866],{"class":5865},"sFweD","Object",[140,5868,1693],{"class":585},[140,5870,5871],{"class":5679}," mailOptions",[140,5873,5874],{"class":1650}," - Opcje wiadomości\n",[140,5876,5878,5880,5882,5884,5886,5889,5891,5894],{"class":142,"line":5877},71,[140,5879,5853],{"class":1650},[140,5881,5856],{"class":585},[140,5883,5860],{"class":5859},[140,5885,1068],{"class":585},[140,5887,5888],{"class":5865},"string",[140,5890,1693],{"class":585},[140,5892,5893],{"class":5679}," mailOptions.to",[140,5895,5896],{"class":1650}," - Adres odbiorcy\n",[140,5898,5900,5902,5904,5906,5908,5910,5912,5915],{"class":142,"line":5899},72,[140,5901,5853],{"class":1650},[140,5903,5856],{"class":585},[140,5905,5860],{"class":5859},[140,5907,1068],{"class":585},[140,5909,5888],{"class":5865},[140,5911,1693],{"class":585},[140,5913,5914],{"class":5679}," mailOptions.subject",[140,5916,5917],{"class":1650}," - Temat wiadomości\n",[140,5919,5921,5923,5925,5927,5929,5931,5933,5936],{"class":142,"line":5920},73,[140,5922,5853],{"class":1650},[140,5924,5856],{"class":585},[140,5926,5860],{"class":5859},[140,5928,1068],{"class":585},[140,5930,5888],{"class":5865},[140,5932,1693],{"class":585},[140,5934,5935],{"class":5679}," mailOptions.body",[140,5937,5938],{"class":1650}," - Treść wiadomości (HTML lub tekst)\n",[140,5940,5942,5944,5946,5948,5950,5952,5954,5956,5959,5961,5964,5967],{"class":142,"line":5941},74,[140,5943,5853],{"class":1650},[140,5945,5856],{"class":585},[140,5947,5860],{"class":5859},[140,5949,1068],{"class":585},[140,5951,5888],{"class":5865},[140,5953,1693],{"class":585},[140,5955,5394],{"class":585},[140,5957,5958],{"class":5679},"mailOptions.bodyType",[140,5960,158],{"class":585},[140,5962,5963],{"class":5679},"'HTML'",[140,5965,5966],{"class":585},"]",[140,5968,5969],{"class":1650}," - Typ treści: 'HTML' lub 'Text'\n",[140,5971,5973,5975,5977,5979,5981,5984,5986,5988,5991,5993],{"class":142,"line":5972},75,[140,5974,5853],{"class":1650},[140,5976,5856],{"class":585},[140,5978,5860],{"class":5859},[140,5980,1068],{"class":585},[140,5982,5983],{"class":5865},"Array",[140,5985,1693],{"class":585},[140,5987,5394],{"class":585},[140,5989,5990],{"class":5679},"mailOptions.attachments",[140,5992,5966],{"class":585},[140,5994,5995],{"class":1650}," - Załączniki (opcjonalne)\n",[140,5997,5999,6001,6003,6006,6008,6011,6013],{"class":142,"line":5998},76,[140,6000,5853],{"class":1650},[140,6002,5856],{"class":585},[140,6004,6005],{"class":5859},"returns",[140,6007,1068],{"class":585},[140,6009,6010],{"class":5865},"Promise\u003CObject>",[140,6012,1693],{"class":585},[140,6014,6015],{"class":1650}," Wynik wysyłania\n",[140,6017,6019],{"class":142,"line":6018},77,[140,6020,5629],{"class":1650},[140,6022,6024,6026,6029,6031,6034,6037],{"class":142,"line":6023},78,[140,6025,5365],{"class":154},[140,6027,6028],{"class":150}," sendMail",[140,6030,1053],{"class":146},[140,6032,6033],{"class":5679},"mailOptions",[140,6035,6036],{"class":146},")",[140,6038,3902],{"class":146},[140,6040,6042,6045,6047,6050,6052,6054],{"class":142,"line":6041},79,[140,6043,6044],{"class":585},"    if",[140,6046,303],{"class":150},[140,6048,6049],{"class":146},"!this.",[140,6051,5581],{"class":556},[140,6053,5474],{"class":150},[140,6055,3887],{"class":146},[140,6057,6059,6062,6064,6067],{"class":142,"line":6058},80,[140,6060,6061],{"class":585},"      await",[140,6063,5433],{"class":146},[140,6065,6066],{"class":1038},"initialize",[140,6068,1111],{"class":150},[140,6070,6072],{"class":142,"line":6071},81,[140,6073,4074],{"class":146},[140,6075,6077],{"class":142,"line":6076},82,[140,6078,574],{"emptyLinePlaceholder":10},[140,6080,6082,6084,6086,6089,6091,6094,6096,6099,6101,6104,6106,6108,6111,6113,6115,6118,6120,6123,6125,6127],{"class":142,"line":6081},83,[140,6083,5377],{"class":154},[140,6085,1068],{"class":146},[140,6087,6088],{"class":556}," to",[140,6090,1060],{"class":146},[140,6092,6093],{"class":556}," subject",[140,6095,1060],{"class":146},[140,6097,6098],{"class":556}," body",[140,6100,1060],{"class":146},[140,6102,6103],{"class":556}," bodyType",[140,6105,5252],{"class":146},[140,6107,382],{"class":146},[140,6109,6110],{"class":164},"HTML",[140,6112,1081],{"class":146},[140,6114,1060],{"class":146},[140,6116,6117],{"class":556}," attachments",[140,6119,5252],{"class":146},[140,6121,6122],{"class":150}," [] ",[140,6124,1693],{"class":146},[140,6126,5252],{"class":146},[140,6128,6129],{"class":556}," mailOptions\n",[140,6131,6133],{"class":142,"line":6132},84,[140,6134,574],{"emptyLinePlaceholder":10},[140,6136,6138,6140,6142,6145,6148,6151,6154,6157,6159,6161,6164,6166],{"class":142,"line":6137},85,[140,6139,6044],{"class":585},[140,6141,303],{"class":150},[140,6143,6144],{"class":146},"!",[140,6146,6147],{"class":556},"to",[140,6149,6150],{"class":146}," ||",[140,6152,6153],{"class":146}," !",[140,6155,6156],{"class":556},"subject",[140,6158,6150],{"class":146},[140,6160,6153],{"class":146},[140,6162,6163],{"class":556},"body",[140,6165,5474],{"class":150},[140,6167,3887],{"class":146},[140,6169,6171,6173,6175,6178,6180,6182,6185,6187],{"class":142,"line":6170},86,[140,6172,5506],{"class":585},[140,6174,5255],{"class":146},[140,6176,6177],{"class":1038}," Error",[140,6179,1053],{"class":150},[140,6181,1081],{"class":146},[140,6183,6184],{"class":164},"Parametry to, subject i body są wymagane",[140,6186,1081],{"class":146},[140,6188,1101],{"class":150},[140,6190,6192],{"class":142,"line":6191},87,[140,6193,4074],{"class":146},[140,6195,6197],{"class":142,"line":6196},88,[140,6198,574],{"emptyLinePlaceholder":10},[140,6200,6202,6204,6207,6209],{"class":142,"line":6201},89,[140,6203,5377],{"class":154},[140,6205,6206],{"class":556}," message",[140,6208,5252],{"class":146},[140,6210,3902],{"class":146},[140,6212,6214,6217,6219,6221],{"class":142,"line":6213},90,[140,6215,6216],{"class":150},"      subject",[140,6218,494],{"class":146},[140,6220,6093],{"class":556},[140,6222,3938],{"class":146},[140,6224,6226,6229,6231],{"class":142,"line":6225},91,[140,6227,6228],{"class":150},"      body",[140,6230,494],{"class":146},[140,6232,3902],{"class":146},[140,6234,6236,6239,6241,6243],{"class":142,"line":6235},92,[140,6237,6238],{"class":150},"        contentType",[140,6240,494],{"class":146},[140,6242,6103],{"class":556},[140,6244,3938],{"class":146},[140,6246,6248,6251,6253],{"class":142,"line":6247},93,[140,6249,6250],{"class":150},"        content",[140,6252,494],{"class":146},[140,6254,6255],{"class":556}," body\n",[140,6257,6259],{"class":142,"line":6258},94,[140,6260,6261],{"class":146},"      },\n",[140,6263,6265,6268,6270],{"class":142,"line":6264},95,[140,6266,6267],{"class":150},"      toRecipients",[140,6269,494],{"class":146},[140,6271,1613],{"class":150},[140,6273,6275],{"class":142,"line":6274},96,[140,6276,6277],{"class":146},"        {\n",[140,6279,6281,6284,6286],{"class":142,"line":6280},97,[140,6282,6283],{"class":150},"          emailAddress",[140,6285,494],{"class":146},[140,6287,3902],{"class":146},[140,6289,6291,6294,6296],{"class":142,"line":6290},98,[140,6292,6293],{"class":150},"            address",[140,6295,494],{"class":146},[140,6297,6298],{"class":556}," to\n",[140,6300,6302],{"class":142,"line":6301},99,[140,6303,5764],{"class":146},[140,6305,6307],{"class":142,"line":6306},100,[140,6308,5769],{"class":146},[140,6310,6312],{"class":142,"line":6311},101,[140,6313,6314],{"class":150},"      ]\n",[140,6316,6318],{"class":142,"line":6317},102,[140,6319,4074],{"class":146},[140,6321,6323],{"class":142,"line":6322},103,[140,6324,574],{"emptyLinePlaceholder":10},[140,6326,6328],{"class":142,"line":6327},104,[140,6329,6330],{"class":1650},"    \u002F\u002F Dodanie załączników jeśli są dostępne\n",[140,6332,6334,6336,6338,6341,6344,6346,6348,6351,6354,6356,6358],{"class":142,"line":6333},105,[140,6335,6044],{"class":585},[140,6337,303],{"class":150},[140,6339,6340],{"class":556},"attachments",[140,6342,6343],{"class":146}," &&",[140,6345,6117],{"class":556},[140,6347,56],{"class":146},[140,6349,6350],{"class":556},"length",[140,6352,6353],{"class":146}," >",[140,6355,1170],{"class":1056},[140,6357,5474],{"class":150},[140,6359,3887],{"class":146},[140,6361,6363,6366,6368,6370,6372,6374,6376,6379,6381,6384,6386,6388],{"class":142,"line":6362},106,[140,6364,6365],{"class":556},"      message",[140,6367,56],{"class":146},[140,6369,6340],{"class":556},[140,6371,5252],{"class":146},[140,6373,6117],{"class":556},[140,6375,56],{"class":146},[140,6377,6378],{"class":1038},"map",[140,6380,1053],{"class":150},[140,6382,6383],{"class":5679},"attachment",[140,6385,5683],{"class":154},[140,6387,303],{"class":150},[140,6389,3887],{"class":146},[140,6391,6393,6396,6399,6401,6403,6405,6408,6410],{"class":142,"line":6392},107,[140,6394,6395],{"class":146},"        '",[140,6397,6398],{"class":150},"@odata.type",[140,6400,1081],{"class":146},[140,6402,494],{"class":146},[140,6404,382],{"class":146},[140,6406,6407],{"class":164},"#microsoft.graph.fileAttachment",[140,6409,1081],{"class":146},[140,6411,3938],{"class":146},[140,6413,6415,6417,6420,6422,6424,6427,6429,6431],{"class":142,"line":6414},108,[140,6416,6395],{"class":146},[140,6418,6419],{"class":150},"name",[140,6421,1081],{"class":146},[140,6423,494],{"class":146},[140,6425,6426],{"class":556}," attachment",[140,6428,56],{"class":146},[140,6430,6419],{"class":556},[140,6432,3938],{"class":146},[140,6434,6436,6438,6441,6443,6445,6447,6449,6451,6453],{"class":142,"line":6435},109,[140,6437,6395],{"class":146},[140,6439,6440],{"class":150},"contentBytes",[140,6442,1081],{"class":146},[140,6444,494],{"class":146},[140,6446,6426],{"class":556},[140,6448,56],{"class":146},[140,6450,6440],{"class":556},[140,6452,1060],{"class":146},[140,6454,6455],{"class":1650}," \u002F\u002F Base64 encoded\n",[140,6457,6459,6461,6464,6466,6468,6470,6472,6474,6476,6478,6481],{"class":142,"line":6458},110,[140,6460,6395],{"class":146},[140,6462,6463],{"class":150},"contentType",[140,6465,1081],{"class":146},[140,6467,494],{"class":146},[140,6469,6426],{"class":556},[140,6471,56],{"class":146},[140,6473,6463],{"class":556},[140,6475,6150],{"class":146},[140,6477,382],{"class":146},[140,6479,6480],{"class":164},"application\u002Foctet-stream",[140,6482,388],{"class":146},[140,6484,6486,6488],{"class":142,"line":6485},111,[140,6487,5774],{"class":146},[140,6489,1850],{"class":150},[140,6491,6493],{"class":142,"line":6492},112,[140,6494,4074],{"class":146},[140,6496,6498],{"class":142,"line":6497},113,[140,6499,574],{"emptyLinePlaceholder":10},[140,6501,6503,6505],{"class":142,"line":6502},114,[140,6504,5416],{"class":585},[140,6506,3902],{"class":146},[140,6508,6510,6512,6514,6516,6518,6520,6522,6524,6527,6529,6531,6534,6536,6539,6541,6543,6546,6548,6550,6552,6555,6557],{"class":142,"line":6509},115,[140,6511,5423],{"class":154},[140,6513,5426],{"class":556},[140,6515,5252],{"class":146},[140,6517,1035],{"class":585},[140,6519,5433],{"class":146},[140,6521,5581],{"class":556},[140,6523,56],{"class":146},[140,6525,6526],{"class":1038},"api",[140,6528,1053],{"class":150},[140,6530,598],{"class":146},[140,6532,6533],{"class":164},"\u002Fusers\u002F",[140,6535,5326],{"class":146},[140,6537,6538],{"class":146},"this.",[140,6540,5593],{"class":556},[140,6542,1693],{"class":146},[140,6544,6545],{"class":164},"\u002FsendMail",[140,6547,598],{"class":146},[140,6549,6036],{"class":150},[140,6551,56],{"class":146},[140,6553,6554],{"class":1038},"post",[140,6556,1053],{"class":150},[140,6558,3887],{"class":146},[140,6560,6562,6565,6567],{"class":142,"line":6561},116,[140,6563,6564],{"class":150},"        message",[140,6566,494],{"class":146},[140,6568,6569],{"class":556}," message\n",[140,6571,6573,6575],{"class":142,"line":6572},117,[140,6574,5774],{"class":146},[140,6576,1101],{"class":150},[140,6578,6580],{"class":142,"line":6579},118,[140,6581,574],{"emptyLinePlaceholder":10},[140,6583,6585,6587,6589,6592,6594,6596,6599,6601,6603,6606],{"class":142,"line":6584},119,[140,6586,5481],{"class":556},[140,6588,56],{"class":146},[140,6590,6591],{"class":1038},"log",[140,6593,1053],{"class":150},[140,6595,598],{"class":146},[140,6597,6598],{"class":164},"Email wysłany pomyślnie do: ",[140,6600,5326],{"class":146},[140,6602,6147],{"class":556},[140,6604,6605],{"class":146},"}`",[140,6607,1101],{"class":150},[140,6609,6611,6613],{"class":142,"line":6610},120,[140,6612,5452],{"class":585},[140,6614,3902],{"class":146},[140,6616,6618,6621,6623,6627],{"class":142,"line":6617},121,[140,6619,6620],{"class":150},"        success",[140,6622,494],{"class":146},[140,6624,6626],{"class":6625},"sfNiH"," true",[140,6628,3938],{"class":146},[140,6630,6632,6634,6636,6638,6641,6643],{"class":142,"line":6631},122,[140,6633,6564],{"class":150},[140,6635,494],{"class":146},[140,6637,382],{"class":146},[140,6639,6640],{"class":164},"Email wysłany pomyślnie",[140,6642,1081],{"class":146},[140,6644,3938],{"class":146},[140,6646,6648,6651,6653],{"class":142,"line":6647},123,[140,6649,6650],{"class":150},"        response",[140,6652,494],{"class":146},[140,6654,6655],{"class":556}," response\n",[140,6657,6659],{"class":142,"line":6658},124,[140,6660,5345],{"class":146},[140,6662,6664,6666,6668,6670,6672,6674],{"class":142,"line":6663},125,[140,6665,5350],{"class":146},[140,6667,5466],{"class":585},[140,6669,303],{"class":150},[140,6671,5471],{"class":556},[140,6673,5474],{"class":150},[140,6675,3887],{"class":146},[140,6677,6679,6681,6683,6685,6687,6689,6692,6694,6696,6698],{"class":142,"line":6678},126,[140,6680,5481],{"class":556},[140,6682,56],{"class":146},[140,6684,5471],{"class":1038},[140,6686,1053],{"class":150},[140,6688,1081],{"class":146},[140,6690,6691],{"class":164},"Błąd podczas wysyłania emaila:",[140,6693,1081],{"class":146},[140,6695,1060],{"class":146},[140,6697,5499],{"class":556},[140,6699,1101],{"class":150},[140,6701,6703,6705],{"class":142,"line":6702},127,[140,6704,5452],{"class":585},[140,6706,3902],{"class":146},[140,6708,6710,6712,6714,6717],{"class":142,"line":6709},128,[140,6711,6620],{"class":150},[140,6713,494],{"class":146},[140,6715,6716],{"class":6625}," false",[140,6718,3938],{"class":146},[140,6720,6722,6724,6726,6728,6731,6733],{"class":142,"line":6721},129,[140,6723,6564],{"class":150},[140,6725,494],{"class":146},[140,6727,382],{"class":146},[140,6729,6730],{"class":164},"Błąd podczas wysyłania emaila",[140,6732,1081],{"class":146},[140,6734,3938],{"class":146},[140,6736,6738,6741,6743,6745,6747],{"class":142,"line":6737},130,[140,6739,6740],{"class":150},"        error",[140,6742,494],{"class":146},[140,6744,5499],{"class":556},[140,6746,56],{"class":146},[140,6748,6749],{"class":556},"message\n",[140,6751,6753],{"class":142,"line":6752},131,[140,6754,5345],{"class":146},[140,6756,6758],{"class":142,"line":6757},132,[140,6759,4074],{"class":146},[140,6761,6763],{"class":142,"line":6762},133,[140,6764,4079],{"class":146},[140,6766,6768],{"class":142,"line":6767},134,[140,6769,574],{"emptyLinePlaceholder":10},[140,6771,6773],{"class":142,"line":6772},135,[140,6774,5619],{"class":1650},[140,6776,6778],{"class":142,"line":6777},136,[140,6779,6780],{"class":1650},"   * Sprawdza połączenie z MS Graph API\n",[140,6782,6784,6786,6788,6790,6792,6795,6797],{"class":142,"line":6783},137,[140,6785,5853],{"class":1650},[140,6787,5856],{"class":585},[140,6789,6005],{"class":5859},[140,6791,1068],{"class":585},[140,6793,6794],{"class":5865},"Promise\u003Cboolean>",[140,6796,1693],{"class":585},[140,6798,6799],{"class":1650}," True jeśli połączenie działa\n",[140,6801,6803],{"class":142,"line":6802},138,[140,6804,5629],{"class":1650},[140,6806,6808,6810,6813,6815],{"class":142,"line":6807},139,[140,6809,5365],{"class":154},[140,6811,6812],{"class":150}," testConnection",[140,6814,1628],{"class":146},[140,6816,3902],{"class":146},[140,6818,6820,6822],{"class":142,"line":6819},140,[140,6821,5416],{"class":585},[140,6823,3902],{"class":146},[140,6825,6827,6830,6832,6834,6836,6838],{"class":142,"line":6826},141,[140,6828,6829],{"class":585},"      if",[140,6831,303],{"class":150},[140,6833,6049],{"class":146},[140,6835,5581],{"class":556},[140,6837,5474],{"class":150},[140,6839,3887],{"class":146},[140,6841,6843,6846,6848,6850],{"class":142,"line":6842},142,[140,6844,6845],{"class":585},"        await",[140,6847,5433],{"class":146},[140,6849,6066],{"class":1038},[140,6851,1111],{"class":150},[140,6853,6855],{"class":142,"line":6854},143,[140,6856,5345],{"class":146},[140,6858,6860],{"class":142,"line":6859},144,[140,6861,574],{"emptyLinePlaceholder":10},[140,6863,6865],{"class":142,"line":6864},145,[140,6866,6867],{"class":1650},"      \u002F\u002F Testujemy połączenie sprawdzając token dostępu\n",[140,6869,6871,6873,6875,6877,6879,6881,6883,6885,6887],{"class":142,"line":6870},146,[140,6872,5423],{"class":154},[140,6874,5700],{"class":556},[140,6876,5252],{"class":146},[140,6878,1035],{"class":585},[140,6880,5433],{"class":146},[140,6882,5566],{"class":556},[140,6884,56],{"class":146},[140,6886,5713],{"class":1038},[140,6888,1111],{"class":150},[140,6890,6892,6894,6896,6899,6901,6903,6905,6907,6909,6911,6913],{"class":142,"line":6891},147,[140,6893,6829],{"class":585},[140,6895,303],{"class":150},[140,6897,6898],{"class":556},"accessToken",[140,6900,6343],{"class":146},[140,6902,5700],{"class":556},[140,6904,56],{"class":146},[140,6906,6350],{"class":556},[140,6908,6353],{"class":146},[140,6910,1170],{"class":1056},[140,6912,5474],{"class":150},[140,6914,3887],{"class":146},[140,6916,6918,6921,6923,6925,6927,6929,6932,6934],{"class":142,"line":6917},148,[140,6919,6920],{"class":556},"        console",[140,6922,56],{"class":146},[140,6924,6591],{"class":1038},[140,6926,1053],{"class":150},[140,6928,1081],{"class":146},[140,6930,6931],{"class":164},"Połączenie z MS Graph API działa. Token otrzymany pomyślnie.",[140,6933,1081],{"class":146},[140,6935,1101],{"class":150},[140,6937,6939,6942],{"class":142,"line":6938},149,[140,6940,6941],{"class":585},"        return",[140,6943,6944],{"class":6625}," true\n",[140,6946,6948,6950,6953],{"class":142,"line":6947},150,[140,6949,5774],{"class":146},[140,6951,6952],{"class":585}," else",[140,6954,3902],{"class":146},[140,6956,6958,6960,6962,6964,6966,6968,6971,6973],{"class":142,"line":6957},151,[140,6959,6920],{"class":556},[140,6961,56],{"class":146},[140,6963,6591],{"class":1038},[140,6965,1053],{"class":150},[140,6967,1081],{"class":146},[140,6969,6970],{"class":164},"Nie udało się uzyskać tokenu dostępu.",[140,6972,1081],{"class":146},[140,6974,1101],{"class":150},[140,6976,6978,6980],{"class":142,"line":6977},152,[140,6979,6941],{"class":585},[140,6981,6982],{"class":6625}," false\n",[140,6984,6986],{"class":142,"line":6985},153,[140,6987,5345],{"class":146},[140,6989,6991,6993,6995,6997,6999,7001],{"class":142,"line":6990},154,[140,6992,5350],{"class":146},[140,6994,5466],{"class":585},[140,6996,303],{"class":150},[140,6998,5471],{"class":556},[140,7000,5474],{"class":150},[140,7002,3887],{"class":146},[140,7004,7006,7008,7010,7012,7014,7016,7019,7021,7023,7025],{"class":142,"line":7005},155,[140,7007,5481],{"class":556},[140,7009,56],{"class":146},[140,7011,5471],{"class":1038},[140,7013,1053],{"class":150},[140,7015,1081],{"class":146},[140,7017,7018],{"class":164},"Błąd połączenia z MS Graph API:",[140,7020,1081],{"class":146},[140,7022,1060],{"class":146},[140,7024,5499],{"class":556},[140,7026,1101],{"class":150},[140,7028,7030,7032],{"class":142,"line":7029},156,[140,7031,5452],{"class":585},[140,7033,6982],{"class":6625},[140,7035,7037],{"class":142,"line":7036},157,[140,7038,4074],{"class":146},[140,7040,7042],{"class":142,"line":7041},158,[140,7043,4079],{"class":146},[140,7045,7047],{"class":142,"line":7046},159,[140,7048,4084],{"class":146},[31,7050,7051,7052,7054],{},"Zgodnie z moimi wytycznymi, powstał też plik ",[41,7053,4856],{},", z testami użycia nowej klasy, ale nie wklejam tutaj zawartości, bo jest sztucznie rozdmuchany, nieczytelny i sprawia wrażenie jakby został przygotowany dla kogoś, kto dopiero uczy się programowania i potrzebuje maksymalnej liczby komunikatów objaśniający co się dzieje w każdej linijce programu.",[31,7056,7057],{},"Kod nie od razu działał bez błędów, ale agent sam zaproponował testowe uruchomienie i po wykryciu błędów (analizując to co dzieje się na konsoli tekstowej), próbował go naprawić. W tym przypadku iterował 3 razy robiąc poprawki i ponownie uruchamiając test.",[3503,7059,7060],{},[31,7061,7062,7063,7066,7067,7069],{},"Uruchomienie kodu, musi być zaaprobowane przez nadzorcę, czyli mnie. Bez takiego zabezpieczenia, agent mógłby wykonać dowolny kod i to w kontekście moich uprawnień. To samo tyczy się aktualizacji wrażliwych plików, takich jak ",[61,7064,7065],{},"package.json",", czy ",[61,7068,783],{},". Agent zawsze czeka na pozwolenie ingerencji w takie pliki, chyba że zezwolimy mu na takie działanie w kontekście identycznej operacji.",[31,7071,7072,7073,7075,7076,7079,7080,7083,7084,7087],{},"Tym czym mnie zaskoczył, to automatycznie wygenerowany plik ",[41,7074,3389],{}," z przydatną dokumentacją, w której są wytyczne jak zdefiniować aplikację w ",[41,7077,7078],{},"Microsoft Azure",", wygenerować dla niej ",[41,7081,7082],{},"klucz API"," i nadać odpowiednie ",[41,7085,7086],{},"uprawnienia",". Podaje też proste przykłady użycia nowej klasy.",[5118,7089,7090],{},[7091,7092,7093,9114],"code-group",{},[131,7094,7096],{"className":4876,"code":7095,"filename":3389,"language":2089,"meta":136,"style":136},"# 📧 ExchangeMailbox - Node.js Email Client\n\nProjekt Node.js implementujący klasę `ExchangeMailbox` do wysyłania wiadomości email przez Microsoft Graph API.\n\n## 🚀 Funkcjonalności\n\n- ✅ **Klasa ExchangeMailbox** z metodą `sendMail`\n- ✅ **Integracja z MS Graph API** do wysyłania emaili\n- ✅ **Uwierzytelnianie** przez Azure AD (MSAL)\n- ✅ **Konfiguracja przez zmienne środowiskowe** (.env)\n- ✅ **Obsługa HTML i tekstowych emaili**\n- ✅ **Wsparcie dla załączników**\n- ✅ **Test połączenia** z MS Graph API\n- ✅ **Przykłady użycia** i testy\n\n## 📋 Wymagania\n\n- Node.js 16+\n- Konto Microsoft 365 \u002F Azure AD\n- Zarejestrowana aplikacja w Azure Portal\n- **Uprawnienia administratora Azure AD** (do zatwierdzenia uprawnień aplikacji)\n\n## 🛠️ Instalacja\n\n1. **Sklonuj\u002Fpobierz projekt**\n2. **Zainstaluj zależności:**\n\n   ```bash\n   npm install\n   ```\n\n3. **Skonfiguruj zmienne środowiskowe** (zobacz sekcję konfiguracja)\n\n4. **Uruchom test:**\n   ```bash\n   npm start\n   ```\n\n## ⚙️ Konfiguracja Azure AD\n\n### 1. Rejestracja aplikacji w Azure Portal\n\n1. Przejdź do [Azure Portal](https:\u002F\u002Fportal.azure.com)\n2. Wybierz **Azure Active Directory** > **App registrations** > **New registration**\n3. Podaj nazwę aplikacji (np. \"ExchangeMailbox App\")\n4. Wybierz **Accounts in this organizational directory only**\n5. Kliknij **Register**\n\n### 2. Konfiguracja uprawnień API\n\n1. W zarejestrowanej aplikacji przejdź do **API permissions**\n2. Kliknij **Add a permission** > **Microsoft Graph** > **Application permissions**\n3. Dodaj uprawnienie:\n   - `Mail.Send` - do wysyłania emaili w imieniu aplikacji\n4. Kliknij **Grant admin consent** (wymagane uprawnienia administratora)\n\n> **Uwaga:** Używamy uwierzytelniania aplikacji (Client Credentials flow), które pozwala aplikacji działać bez logowania użytkownika, ale wymaga uprawnień administratora.\n\n### 3. Utworzenie Client Secret\n\n1. Przejdź do **Certificates & secrets**\n2. Kliknij **New client secret**\n3. Podaj opis i wybierz okres ważności\n4. **Skopiuj wartość** - będzie potrzebna w pliku .env\n\n### 4. Skopiuj identyfikatory\n\nZ sekcji **Overview** skopiuj:\n\n- **Application (client) ID**\n- **Directory (tenant) ID**\n\n## 🔧 Konfiguracja zmiennych środowiskowych\n\nSkopiuj plik `.env` i wypełnij następujące wartości:\n\n```env\n# MS Graph API Configuration\nAZURE_CLIENT_ID=your_client_id_here\nAZURE_CLIENT_SECRET=your_client_secret_here\nAZURE_TENANT_ID=your_tenant_id_here\nSENDER_EMAIL=your_sender_email@yourdomain.com\nTEST_RECIPIENT_EMAIL=test_recipient@example.com\n```\n\n### Gdzie znaleźć te wartości:\n\n- **AZURE_CLIENT_ID**: Application ID z Azure Portal\n- **AZURE_CLIENT_SECRET**: Client Secret utworzony w Azure Portal\n- **AZURE_TENANT_ID**: Directory ID z Azure Portal\n- **SENDER_EMAIL**: Email użytkownika, który będzie nadawcą (musi istnieć w organizacji)\n- **TEST_RECIPIENT_EMAIL**: Email do testów (opcjonalne)\n\n## 📖 Użycie\n\n### Podstawowy przykład\n\n```javascript\nimport { ExchangeMailbox } from '.\u002FExchangeMailbox.js'\n\nconst mailbox = new ExchangeMailbox()\n\n\u002F\u002F Wysłanie prostego emaila\nconst result = await mailbox.sendMail({\n  to: 'odbiorca@example.com',\n  subject: 'Temat wiadomości',\n  body: 'Treść wiadomości'\n})\n\nif (result.success) {\n  console.log('Email wysłany pomyślnie!')\n} else {\n  console.error('Błąd:', result.message)\n}\n```\n\n### Email z HTML\n\n```javascript\nawait mailbox.sendMail({\n  to: 'odbiorca@example.com',\n  subject: 'Email z HTML',\n  body: '\u003Ch1>Witaj!\u003C\u002Fh1>\u003Cp>To jest \u003Cstrong>HTML\u003C\u002Fstrong> email.\u003C\u002Fp>',\n  bodyType: 'HTML'\n})\n```\n\n### Email z załącznikami\n\n```javascript\nawait mailbox.sendMail({\n  to: 'odbiorca@example.com',\n  subject: 'Email z załącznikiem',\n  body: 'Zobacz załącznik w załączniku',\n  attachments: [\n    {\n      name: 'dokument.pdf',\n      contentBytes: 'base64_encoded_file_content',\n      contentType: 'application\u002Fpdf'\n    }\n  ]\n})\n```\n\n### Test połączenia\n\n```javascript\nconst isConnected = await mailbox.testConnection()\nif (isConnected) {\n  console.log('Połączenie z MS Graph API działa!')\n}\n```\n\n> **Uwaga:** Test połączenia sprawdza czy można uzyskać token dostępu z Azure AD. Nie wymaga dodatkowych uprawnień API.\n\n## 🏃 Uruchomienie testów\n\n```bash\n# Uruchomienie testów\nnpm start\n\n# lub bezpośrednio\nnode main.js\n```\n\n## 📁 Struktura projektu\n\n```\nexchange-mailbox-nodejs\u002F\n├── 📄 ExchangeMailbox.js    # Główna klasa do wysyłania emaili\n├── 📄 main.js              # Testy i przykłady użycia\n├── 📄 package.json         # Konfiguracja NPM i zależności\n├── 📄 .env                 # Zmienne środowiskowe (konfiguracja)\n├── 📄 .gitignore          # Wykluczenia Git (chroni .env)\n├── 📄 README.md           # Dokumentacja (ten plik)\n└── 📁 .github\u002F\n    └── 📄 copilot-instructions.md  # Instrukcje dla Copilot\n```\n\n## 🔍 Rozwiązywanie problemów\n\n### Błędy uwierzytelniania\n\n1. **\"Invalid client credentials\"** - sprawdź Client ID i Client Secret w pliku .env\n2. **\"Invalid tenant\"** - sprawdź Tenant ID w pliku .env\n3. **\"Insufficient privileges\"** - upewnij się że aplikacja ma uprawnienie `Mail.Send` i zostało zatwierdzone przez administratora\n\n### Błędy wysyłania emaili\n\n1. **\"User not found\"** - sprawdź czy SENDER_EMAIL to prawdziwy użytkownik w organizacji\n2. **\"Access denied\"** - sprawdź czy admin zatwierdził uprawnienia aplikacji\n3. **\"Mailbox not found\"** - upewnij się że użytkownik ma licencję Exchange Online\n\n### Częste problemy podczas testów\n\n1. **\"\u002Fme request is only valid with delegated authentication flow\"** - Ten błąd jest normalny, używamy uwierzytelniania aplikacji (client credentials)\n2. **\"Insufficient privileges to complete the operation\"** - Nie jest potrzebne uprawnienie User.Read dla podstawowej funkcjonalności wysyłania emaili\n3. **\"TypeError: \\_this.provider is not a function\"** - Problem z konfiguracją klienta Graph, rozwiązany w aktualnej wersji\n\n### Debugging\n\n- Test połączenia sprawdza czy można uzyskać token dostępu (nie wymaga dodatkowych uprawnień)\n- Włącz szczegółowe logi błędów w konsoli aby zobaczyć dokładne komunikaty z MS Graph API\n- Sprawdź czy wszystkie zmienne środowiskowe w .env są wypełnione\n\n## 📦 Zależności\n\n- `@azure\u002Fmsal-node` - Uwierzytelnianie Microsoft Authentication Library\n- `@microsoft\u002Fmicrosoft-graph-client` - Klient MS Graph API\n- `dotenv` - Ładowanie zmiennych środowiskowych\n\n## 📄 Licencja\n\nMIT License - możesz swobodnie używać, modyfikować i dystrybuować ten kod.\n\n## 🆘 Pomoc\n\nJeśli napotkasz problemy:\n\n1. Sprawdź konfigurację w Azure Portal\n2. Zweryfikuj zmienne środowiskowe w `.env`\n3. Uruchom test połączenia: `await mailbox.testConnection()`\n4. Sprawdź logi błędów w konsoli\n\n---\n\n**📧 Miłego wysyłania emaili z ExchangeMailbox! 🚀**\n",[61,7097,7098,7105,7109,7123,7127,7134,7138,7161,7177,7193,7209,7223,7236,7252,7268,7272,7279,7283,7290,7297,7304,7319,7323,7330,7334,7345,7356,7360,7369,7374,7379,7383,7397,7401,7413,7419,7424,7428,7432,7439,7443,7451,7455,7477,7510,7517,7530,7545,7549,7556,7560,7574,7605,7612,7627,7643,7647,7663,7667,7674,7678,7691,7704,7711,7725,7729,7736,7740,7755,7759,7770,7781,7785,7792,7796,7810,7814,7822,7826,7831,7836,7841,7846,7851,7856,7860,7867,7871,7884,7897,7910,7923,7936,7940,7947,7951,7958,7962,7969,7988,7992,8007,8011,8016,8038,8054,8070,8084,8090,8094,8109,8129,8137,8166,8170,8174,8178,8185,8189,8195,8210,8224,8239,8254,8267,8273,8277,8281,8288,8292,8298,8312,8326,8341,8356,8365,8370,8386,8402,8416,8420,8425,8431,8435,8439,8446,8450,8456,8476,8485,8504,8508,8512,8516,8529,8533,8540,8544,8550,8555,8564,8569,8575,8583,8588,8593,8601,8606,8611,8617,8623,8629,8635,8641,8647,8653,8659,8665,8670,8675,8683,8688,8696,8701,8716,8731,8755,8760,8768,8773,8788,8803,8818,8823,8831,8836,8851,8866,8888,8893,8901,8906,8914,8922,8930,8935,8943,8948,8962,8976,8990,8995,9003,9008,9014,9019,9027,9032,9038,9043,9051,9065,9080,9088,9093,9099,9104],{"__ignoreMap":136},[140,7099,7100,7102],{"class":142,"line":143},[140,7101,4884],{"class":146},[140,7103,7104],{"class":363},"📧 ExchangeMailbox - Node.js Email Client\n",[140,7106,7107],{"class":142,"line":185},[140,7108,574],{"emptyLinePlaceholder":10},[140,7110,7111,7114,7116,7118,7120],{"class":142,"line":216},[140,7112,7113],{"class":556},"Projekt Node.js implementujący klasę ",[140,7115,598],{"class":146},[140,7117,4814],{"class":164},[140,7119,598],{"class":146},[140,7121,7122],{"class":556}," do wysyłania wiadomości email przez Microsoft Graph API.\n",[140,7124,7125],{"class":142,"line":247},[140,7126,574],{"emptyLinePlaceholder":10},[140,7128,7129,7131],{"class":142,"line":616},[140,7130,4905],{"class":146},[140,7132,7133],{"class":363},"🚀 Funkcjonalności\n",[140,7135,7136],{"class":142,"line":624},[140,7137,574],{"emptyLinePlaceholder":10},[140,7139,7140,7142,7145,7147,7150,7152,7155,7157,7159],{"class":142,"line":641},[140,7141,579],{"class":146},[140,7143,7144],{"class":556}," ✅ ",[140,7146,561],{"class":560},[140,7148,7149],{"class":564},"Klasa ExchangeMailbox",[140,7151,561],{"class":560},[140,7153,7154],{"class":556}," z metodą ",[140,7156,598],{"class":146},[140,7158,4828],{"class":164},[140,7160,4970],{"class":146},[140,7162,7163,7165,7167,7169,7172,7174],{"class":142,"line":649},[140,7164,579],{"class":146},[140,7166,7144],{"class":556},[140,7168,561],{"class":560},[140,7170,7171],{"class":564},"Integracja z MS Graph API",[140,7173,561],{"class":560},[140,7175,7176],{"class":556}," do wysyłania emaili\n",[140,7178,7179,7181,7183,7185,7188,7190],{"class":142,"line":657},[140,7180,579],{"class":146},[140,7182,7144],{"class":556},[140,7184,561],{"class":560},[140,7186,7187],{"class":564},"Uwierzytelnianie",[140,7189,561],{"class":560},[140,7191,7192],{"class":556}," przez Azure AD (MSAL)\n",[140,7194,7195,7197,7199,7201,7204,7206],{"class":142,"line":665},[140,7196,579],{"class":146},[140,7198,7144],{"class":556},[140,7200,561],{"class":560},[140,7202,7203],{"class":564},"Konfiguracja przez zmienne środowiskowe",[140,7205,561],{"class":560},[140,7207,7208],{"class":556}," (.env)\n",[140,7210,7211,7213,7215,7217,7220],{"class":142,"line":704},[140,7212,579],{"class":146},[140,7214,7144],{"class":556},[140,7216,561],{"class":560},[140,7218,7219],{"class":564},"Obsługa HTML i tekstowych emaili",[140,7221,7222],{"class":560},"**\n",[140,7224,7225,7227,7229,7231,7234],{"class":142,"line":730},[140,7226,579],{"class":146},[140,7228,7144],{"class":556},[140,7230,561],{"class":560},[140,7232,7233],{"class":564},"Wsparcie dla załączników",[140,7235,7222],{"class":560},[140,7237,7238,7240,7242,7244,7247,7249],{"class":142,"line":738},[140,7239,579],{"class":146},[140,7241,7144],{"class":556},[140,7243,561],{"class":560},[140,7245,7246],{"class":564},"Test połączenia",[140,7248,561],{"class":560},[140,7250,7251],{"class":556}," z MS Graph API\n",[140,7253,7254,7256,7258,7260,7263,7265],{"class":142,"line":756},[140,7255,579],{"class":146},[140,7257,7144],{"class":556},[140,7259,561],{"class":560},[140,7261,7262],{"class":564},"Przykłady użycia",[140,7264,561],{"class":560},[140,7266,7267],{"class":556}," i testy\n",[140,7269,7270],{"class":142,"line":773},[140,7271,574],{"emptyLinePlaceholder":10},[140,7273,7274,7276],{"class":142,"line":790},[140,7275,4905],{"class":146},[140,7277,7278],{"class":363},"📋 Wymagania\n",[140,7280,7281],{"class":142,"line":1409},[140,7282,574],{"emptyLinePlaceholder":10},[140,7284,7285,7287],{"class":142,"line":1424},[140,7286,579],{"class":146},[140,7288,7289],{"class":556}," Node.js 16+\n",[140,7291,7292,7294],{"class":142,"line":1440},[140,7293,579],{"class":146},[140,7295,7296],{"class":556}," Konto Microsoft 365 \u002F Azure AD\n",[140,7298,7299,7301],{"class":142,"line":1455},[140,7300,579],{"class":146},[140,7302,7303],{"class":556}," Zarejestrowana aplikacja w Azure Portal\n",[140,7305,7306,7308,7311,7314,7316],{"class":142,"line":1510},[140,7307,579],{"class":146},[140,7309,7310],{"class":560}," **",[140,7312,7313],{"class":564},"Uprawnienia administratora Azure AD",[140,7315,561],{"class":560},[140,7317,7318],{"class":556}," (do zatwierdzenia uprawnień aplikacji)\n",[140,7320,7321],{"class":142,"line":2356},[140,7322,574],{"emptyLinePlaceholder":10},[140,7324,7325,7327],{"class":142,"line":2362},[140,7326,4905],{"class":146},[140,7328,7329],{"class":363},"🛠️ Instalacja\n",[140,7331,7332],{"class":142,"line":2367},[140,7333,574],{"emptyLinePlaceholder":10},[140,7335,7336,7338,7340,7343],{"class":142,"line":2373},[140,7337,1263],{"class":146},[140,7339,7310],{"class":560},[140,7341,7342],{"class":564},"Sklonuj\u002Fpobierz projekt",[140,7344,7222],{"class":560},[140,7346,7347,7349,7351,7354],{"class":142,"line":2378},[140,7348,1280],{"class":146},[140,7350,7310],{"class":560},[140,7352,7353],{"class":564},"Zainstaluj zależności:",[140,7355,7222],{"class":560},[140,7357,7358],{"class":142,"line":2384},[140,7359,574],{"emptyLinePlaceholder":10},[140,7361,7362,7365],{"class":142,"line":2390},[140,7363,7364],{"class":164},"   ```",[140,7366,7368],{"class":7367},"sJsPd","bash\n",[140,7370,7371],{"class":142,"line":2396},[140,7372,7373],{"class":556},"   npm install\n",[140,7375,7376],{"class":142,"line":2402},[140,7377,7378],{"class":164},"   ```\n",[140,7380,7381],{"class":142,"line":2408},[140,7382,574],{"emptyLinePlaceholder":10},[140,7384,7385,7387,7389,7392,7394],{"class":142,"line":2414},[140,7386,4982],{"class":146},[140,7388,7310],{"class":560},[140,7390,7391],{"class":564},"Skonfiguruj zmienne środowiskowe",[140,7393,561],{"class":560},[140,7395,7396],{"class":556}," (zobacz sekcję konfiguracja)\n",[140,7398,7399],{"class":142,"line":2419},[140,7400,574],{"emptyLinePlaceholder":10},[140,7402,7403,7406,7408,7411],{"class":142,"line":2425},[140,7404,7405],{"class":146},"4.",[140,7407,7310],{"class":560},[140,7409,7410],{"class":564},"Uruchom test:",[140,7412,7222],{"class":560},[140,7414,7415,7417],{"class":142,"line":2430},[140,7416,7364],{"class":164},[140,7418,7368],{"class":7367},[140,7420,7421],{"class":142,"line":2436},[140,7422,7423],{"class":556},"   npm start\n",[140,7425,7426],{"class":142,"line":2442},[140,7427,7378],{"class":164},[140,7429,7430],{"class":142,"line":2448},[140,7431,574],{"emptyLinePlaceholder":10},[140,7433,7434,7436],{"class":142,"line":2454},[140,7435,4905],{"class":146},[140,7437,7438],{"class":363},"⚙️ Konfiguracja Azure AD\n",[140,7440,7441],{"class":142,"line":2460},[140,7442,574],{"emptyLinePlaceholder":10},[140,7444,7445,7448],{"class":142,"line":2466},[140,7446,7447],{"class":146},"### ",[140,7449,7450],{"class":363},"1. Rejestracja aplikacji w Azure Portal\n",[140,7452,7453],{"class":142,"line":2472},[140,7454,574],{"emptyLinePlaceholder":10},[140,7456,7457,7459,7462,7465,7468,7471,7475],{"class":142,"line":2477},[140,7458,1263],{"class":146},[140,7460,7461],{"class":556}," Przejdź do ",[140,7463,7464],{"class":146},"[",[140,7466,7467],{"class":164},"Azure Portal",[140,7469,7470],{"class":146},"](",[140,7472,7474],{"class":7473},"sDpXG","https:\u002F\u002Fportal.azure.com",[140,7476,1101],{"class":146},[140,7478,7479,7481,7484,7486,7489,7491,7494,7496,7499,7501,7503,7505,7508],{"class":142,"line":2483},[140,7480,1280],{"class":146},[140,7482,7483],{"class":556}," Wybierz ",[140,7485,561],{"class":560},[140,7487,7488],{"class":564},"Azure Active Directory",[140,7490,561],{"class":560},[140,7492,7493],{"class":556}," > ",[140,7495,561],{"class":560},[140,7497,7498],{"class":564},"App registrations",[140,7500,561],{"class":560},[140,7502,7493],{"class":556},[140,7504,561],{"class":560},[140,7506,7507],{"class":564},"New registration",[140,7509,7222],{"class":560},[140,7511,7512,7514],{"class":142,"line":2489},[140,7513,4982],{"class":146},[140,7515,7516],{"class":556}," Podaj nazwę aplikacji (np. \"ExchangeMailbox App\")\n",[140,7518,7519,7521,7523,7525,7528],{"class":142,"line":2495},[140,7520,7405],{"class":146},[140,7522,7483],{"class":556},[140,7524,561],{"class":560},[140,7526,7527],{"class":564},"Accounts in this organizational directory only",[140,7529,7222],{"class":560},[140,7531,7532,7535,7538,7540,7543],{"class":142,"line":2501},[140,7533,7534],{"class":146},"5.",[140,7536,7537],{"class":556}," Kliknij ",[140,7539,561],{"class":560},[140,7541,7542],{"class":564},"Register",[140,7544,7222],{"class":560},[140,7546,7547],{"class":142,"line":2507},[140,7548,574],{"emptyLinePlaceholder":10},[140,7550,7551,7553],{"class":142,"line":2513},[140,7552,7447],{"class":146},[140,7554,7555],{"class":363},"2. Konfiguracja uprawnień API\n",[140,7557,7558],{"class":142,"line":2519},[140,7559,574],{"emptyLinePlaceholder":10},[140,7561,7562,7564,7567,7569,7572],{"class":142,"line":2525},[140,7563,1263],{"class":146},[140,7565,7566],{"class":556}," W zarejestrowanej aplikacji przejdź do ",[140,7568,561],{"class":560},[140,7570,7571],{"class":564},"API permissions",[140,7573,7222],{"class":560},[140,7575,7576,7578,7580,7582,7585,7587,7589,7591,7594,7596,7598,7600,7603],{"class":142,"line":2531},[140,7577,1280],{"class":146},[140,7579,7537],{"class":556},[140,7581,561],{"class":560},[140,7583,7584],{"class":564},"Add a permission",[140,7586,561],{"class":560},[140,7588,7493],{"class":556},[140,7590,561],{"class":560},[140,7592,7593],{"class":564},"Microsoft Graph",[140,7595,561],{"class":560},[140,7597,7493],{"class":556},[140,7599,561],{"class":560},[140,7601,7602],{"class":564},"Application permissions",[140,7604,7222],{"class":560},[140,7606,7607,7609],{"class":142,"line":2537},[140,7608,4982],{"class":146},[140,7610,7611],{"class":556}," Dodaj uprawnienie:\n",[140,7613,7614,7617,7619,7622,7624],{"class":142,"line":2543},[140,7615,7616],{"class":146},"   -",[140,7618,5320],{"class":146},[140,7620,7621],{"class":164},"Mail.Send",[140,7623,598],{"class":146},[140,7625,7626],{"class":556}," - do wysyłania emaili w imieniu aplikacji\n",[140,7628,7629,7631,7633,7635,7638,7640],{"class":142,"line":2548},[140,7630,7405],{"class":146},[140,7632,7537],{"class":556},[140,7634,561],{"class":560},[140,7636,7637],{"class":564},"Grant admin consent",[140,7639,561],{"class":560},[140,7641,7642],{"class":556}," (wymagane uprawnienia administratora)\n",[140,7644,7645],{"class":142,"line":2554},[140,7646,574],{"emptyLinePlaceholder":10},[140,7648,7649,7653,7655,7658,7660],{"class":142,"line":2559},[140,7650,7652],{"class":7651},"sCNwf",">",[140,7654,7310],{"class":560},[140,7656,7657],{"class":564},"Uwaga:",[140,7659,561],{"class":560},[140,7661,7662],{"class":585}," Używamy uwierzytelniania aplikacji (Client Credentials flow), które pozwala aplikacji działać bez logowania użytkownika, ale wymaga uprawnień administratora.\n",[140,7664,7665],{"class":142,"line":2565},[140,7666,574],{"emptyLinePlaceholder":10},[140,7668,7669,7671],{"class":142,"line":2571},[140,7670,7447],{"class":146},[140,7672,7673],{"class":363},"3. Utworzenie Client Secret\n",[140,7675,7676],{"class":142,"line":2576},[140,7677,574],{"emptyLinePlaceholder":10},[140,7679,7680,7682,7684,7686,7689],{"class":142,"line":2582},[140,7681,1263],{"class":146},[140,7683,7461],{"class":556},[140,7685,561],{"class":560},[140,7687,7688],{"class":564},"Certificates & secrets",[140,7690,7222],{"class":560},[140,7692,7693,7695,7697,7699,7702],{"class":142,"line":2587},[140,7694,1280],{"class":146},[140,7696,7537],{"class":556},[140,7698,561],{"class":560},[140,7700,7701],{"class":564},"New client secret",[140,7703,7222],{"class":560},[140,7705,7706,7708],{"class":142,"line":5793},[140,7707,4982],{"class":146},[140,7709,7710],{"class":556}," Podaj opis i wybierz okres ważności\n",[140,7712,7713,7715,7717,7720,7722],{"class":142,"line":5817},[140,7714,7405],{"class":146},[140,7716,7310],{"class":560},[140,7718,7719],{"class":564},"Skopiuj wartość",[140,7721,561],{"class":560},[140,7723,7724],{"class":556}," - będzie potrzebna w pliku .env\n",[140,7726,7727],{"class":142,"line":5824},[140,7728,574],{"emptyLinePlaceholder":10},[140,7730,7731,7733],{"class":142,"line":5829},[140,7732,7447],{"class":146},[140,7734,7735],{"class":363},"4. Skopiuj identyfikatory\n",[140,7737,7738],{"class":142,"line":5834},[140,7739,574],{"emptyLinePlaceholder":10},[140,7741,7742,7745,7747,7750,7752],{"class":142,"line":5839},[140,7743,7744],{"class":556},"Z sekcji ",[140,7746,561],{"class":560},[140,7748,7749],{"class":564},"Overview",[140,7751,561],{"class":560},[140,7753,7754],{"class":556}," skopiuj:\n",[140,7756,7757],{"class":142,"line":5844},[140,7758,574],{"emptyLinePlaceholder":10},[140,7760,7761,7763,7765,7768],{"class":142,"line":5850},[140,7762,579],{"class":146},[140,7764,7310],{"class":560},[140,7766,7767],{"class":564},"Application (client) ID",[140,7769,7222],{"class":560},[140,7771,7772,7774,7776,7779],{"class":142,"line":5877},[140,7773,579],{"class":146},[140,7775,7310],{"class":560},[140,7777,7778],{"class":564},"Directory (tenant) ID",[140,7780,7222],{"class":560},[140,7782,7783],{"class":142,"line":5899},[140,7784,574],{"emptyLinePlaceholder":10},[140,7786,7787,7789],{"class":142,"line":5920},[140,7788,4905],{"class":146},[140,7790,7791],{"class":363},"🔧 Konfiguracja zmiennych środowiskowych\n",[140,7793,7794],{"class":142,"line":5941},[140,7795,574],{"emptyLinePlaceholder":10},[140,7797,7798,7801,7803,7805,7807],{"class":142,"line":5972},[140,7799,7800],{"class":556},"Skopiuj plik ",[140,7802,598],{"class":146},[140,7804,783],{"class":164},[140,7806,598],{"class":146},[140,7808,7809],{"class":556}," i wypełnij następujące wartości:\n",[140,7811,7812],{"class":142,"line":5998},[140,7813,574],{"emptyLinePlaceholder":10},[140,7815,7816,7819],{"class":142,"line":6018},[140,7817,7818],{"class":164},"```",[140,7820,7821],{"class":7367},"env\n",[140,7823,7824],{"class":142,"line":6023},[140,7825,5009],{"class":7367},[140,7827,7828],{"class":142,"line":6041},[140,7829,7830],{"class":7367},"AZURE_CLIENT_ID=your_client_id_here\n",[140,7832,7833],{"class":142,"line":6058},[140,7834,7835],{"class":7367},"AZURE_CLIENT_SECRET=your_client_secret_here\n",[140,7837,7838],{"class":142,"line":6071},[140,7839,7840],{"class":7367},"AZURE_TENANT_ID=your_tenant_id_here\n",[140,7842,7843],{"class":142,"line":6076},[140,7844,7845],{"class":7367},"SENDER_EMAIL=your_sender_email@yourdomain.com\n",[140,7847,7848],{"class":142,"line":6081},[140,7849,7850],{"class":7367},"TEST_RECIPIENT_EMAIL=test_recipient@example.com\n",[140,7852,7853],{"class":142,"line":6132},[140,7854,7855],{"class":164},"```\n",[140,7857,7858],{"class":142,"line":6137},[140,7859,574],{"emptyLinePlaceholder":10},[140,7861,7862,7864],{"class":142,"line":6170},[140,7863,7447],{"class":146},[140,7865,7866],{"class":363},"Gdzie znaleźć te wartości:\n",[140,7868,7869],{"class":142,"line":6191},[140,7870,574],{"emptyLinePlaceholder":10},[140,7872,7873,7875,7877,7879,7881],{"class":142,"line":6196},[140,7874,579],{"class":146},[140,7876,7310],{"class":560},[140,7878,5028],{"class":564},[140,7880,561],{"class":560},[140,7882,7883],{"class":556},": Application ID z Azure Portal\n",[140,7885,7886,7888,7890,7892,7894],{"class":142,"line":6201},[140,7887,579],{"class":146},[140,7889,7310],{"class":560},[140,7891,5047],{"class":564},[140,7893,561],{"class":560},[140,7895,7896],{"class":556},": Client Secret utworzony w Azure Portal\n",[140,7898,7899,7901,7903,7905,7907],{"class":142,"line":6213},[140,7900,579],{"class":146},[140,7902,7310],{"class":560},[140,7904,5066],{"class":564},[140,7906,561],{"class":560},[140,7908,7909],{"class":556},": Directory ID z Azure Portal\n",[140,7911,7912,7914,7916,7918,7920],{"class":142,"line":6225},[140,7913,579],{"class":146},[140,7915,7310],{"class":560},[140,7917,5085],{"class":564},[140,7919,561],{"class":560},[140,7921,7922],{"class":556},": Email użytkownika, który będzie nadawcą (musi istnieć w organizacji)\n",[140,7924,7925,7927,7929,7931,7933],{"class":142,"line":6235},[140,7926,579],{"class":146},[140,7928,7310],{"class":560},[140,7930,5104],{"class":564},[140,7932,561],{"class":560},[140,7934,7935],{"class":556},": Email do testów (opcjonalne)\n",[140,7937,7938],{"class":142,"line":6247},[140,7939,574],{"emptyLinePlaceholder":10},[140,7941,7942,7944],{"class":142,"line":6258},[140,7943,4905],{"class":146},[140,7945,7946],{"class":363},"📖 Użycie\n",[140,7948,7949],{"class":142,"line":6264},[140,7950,574],{"emptyLinePlaceholder":10},[140,7952,7953,7955],{"class":142,"line":6274},[140,7954,7447],{"class":146},[140,7956,7957],{"class":363},"Podstawowy przykład\n",[140,7959,7960],{"class":142,"line":6280},[140,7961,574],{"emptyLinePlaceholder":10},[140,7963,7964,7966],{"class":142,"line":6290},[140,7965,7818],{"class":164},[140,7967,7968],{"class":7367},"javascript\n",[140,7970,7971,7973,7975,7977,7979,7981,7983,7986],{"class":142,"line":6301},[140,7972,5129],{"class":585},[140,7974,1068],{"class":146},[140,7976,5549],{"class":556},[140,7978,1098],{"class":146},[140,7980,5139],{"class":585},[140,7982,382],{"class":146},[140,7984,7985],{"class":164},".\u002FExchangeMailbox.js",[140,7987,388],{"class":146},[140,7989,7990],{"class":142,"line":6306},[140,7991,574],{"emptyLinePlaceholder":10},[140,7993,7994,7996,7999,8001,8003,8005],{"class":142,"line":6311},[140,7995,1027],{"class":154},[140,7997,7998],{"class":556}," mailbox ",[140,8000,158],{"class":146},[140,8002,5255],{"class":146},[140,8004,5549],{"class":1038},[140,8006,1111],{"class":556},[140,8008,8009],{"class":142,"line":6317},[140,8010,574],{"emptyLinePlaceholder":10},[140,8012,8013],{"class":142,"line":6322},[140,8014,8015],{"class":1650},"\u002F\u002F Wysłanie prostego emaila\n",[140,8017,8018,8020,8023,8025,8027,8030,8032,8034,8036],{"class":142,"line":6327},[140,8019,1027],{"class":154},[140,8021,8022],{"class":556}," result ",[140,8024,158],{"class":146},[140,8026,1035],{"class":585},[140,8028,8029],{"class":556}," mailbox",[140,8031,56],{"class":146},[140,8033,4828],{"class":1038},[140,8035,1053],{"class":556},[140,8037,3887],{"class":146},[140,8039,8040,8043,8045,8047,8050,8052],{"class":142,"line":6333},[140,8041,8042],{"class":150},"  to",[140,8044,494],{"class":146},[140,8046,382],{"class":146},[140,8048,8049],{"class":164},"odbiorca@example.com",[140,8051,1081],{"class":146},[140,8053,3938],{"class":146},[140,8055,8056,8059,8061,8063,8066,8068],{"class":142,"line":6362},[140,8057,8058],{"class":150},"  subject",[140,8060,494],{"class":146},[140,8062,382],{"class":146},[140,8064,8065],{"class":164},"Temat wiadomości",[140,8067,1081],{"class":146},[140,8069,3938],{"class":146},[140,8071,8072,8075,8077,8079,8082],{"class":142,"line":6392},[140,8073,8074],{"class":150},"  body",[140,8076,494],{"class":146},[140,8078,382],{"class":146},[140,8080,8081],{"class":164},"Treść wiadomości",[140,8083,388],{"class":146},[140,8085,8086,8088],{"class":142,"line":6414},[140,8087,1693],{"class":146},[140,8089,1101],{"class":556},[140,8091,8092],{"class":142,"line":6435},[140,8093,574],{"emptyLinePlaceholder":10},[140,8095,8096,8099,8102,8104,8107],{"class":142,"line":6458},[140,8097,8098],{"class":585},"if",[140,8100,8101],{"class":556}," (result",[140,8103,56],{"class":146},[140,8105,8106],{"class":556},"success) ",[140,8108,3887],{"class":146},[140,8110,8111,8114,8116,8118,8120,8122,8125,8127],{"class":142,"line":6485},[140,8112,8113],{"class":556},"  console",[140,8115,56],{"class":146},[140,8117,6591],{"class":1038},[140,8119,1053],{"class":150},[140,8121,1081],{"class":146},[140,8123,8124],{"class":164},"Email wysłany pomyślnie!",[140,8126,1081],{"class":146},[140,8128,1101],{"class":150},[140,8130,8131,8133,8135],{"class":142,"line":6492},[140,8132,1693],{"class":146},[140,8134,6952],{"class":585},[140,8136,3902],{"class":146},[140,8138,8139,8141,8143,8145,8147,8149,8152,8154,8156,8159,8161,8164],{"class":142,"line":6497},[140,8140,8113],{"class":556},[140,8142,56],{"class":146},[140,8144,5471],{"class":1038},[140,8146,1053],{"class":150},[140,8148,1081],{"class":146},[140,8150,8151],{"class":164},"Błąd:",[140,8153,1081],{"class":146},[140,8155,1060],{"class":146},[140,8157,8158],{"class":556}," result",[140,8160,56],{"class":146},[140,8162,8163],{"class":556},"message",[140,8165,1101],{"class":150},[140,8167,8168],{"class":142,"line":6502},[140,8169,4084],{"class":146},[140,8171,8172],{"class":142,"line":6509},[140,8173,7855],{"class":164},[140,8175,8176],{"class":142,"line":6561},[140,8177,574],{"emptyLinePlaceholder":10},[140,8179,8180,8182],{"class":142,"line":6572},[140,8181,7447],{"class":146},[140,8183,8184],{"class":363},"Email z HTML\n",[140,8186,8187],{"class":142,"line":6579},[140,8188,574],{"emptyLinePlaceholder":10},[140,8190,8191,8193],{"class":142,"line":6584},[140,8192,7818],{"class":164},[140,8194,7968],{"class":7367},[140,8196,8197,8200,8202,8204,8206,8208],{"class":142,"line":6610},[140,8198,8199],{"class":585},"await",[140,8201,8029],{"class":556},[140,8203,56],{"class":146},[140,8205,4828],{"class":1038},[140,8207,1053],{"class":556},[140,8209,3887],{"class":146},[140,8211,8212,8214,8216,8218,8220,8222],{"class":142,"line":6617},[140,8213,8042],{"class":150},[140,8215,494],{"class":146},[140,8217,382],{"class":146},[140,8219,8049],{"class":164},[140,8221,1081],{"class":146},[140,8223,3938],{"class":146},[140,8225,8226,8228,8230,8232,8235,8237],{"class":142,"line":6631},[140,8227,8058],{"class":150},[140,8229,494],{"class":146},[140,8231,382],{"class":146},[140,8233,8234],{"class":164},"Email z HTML",[140,8236,1081],{"class":146},[140,8238,3938],{"class":146},[140,8240,8241,8243,8245,8247,8250,8252],{"class":142,"line":6647},[140,8242,8074],{"class":150},[140,8244,494],{"class":146},[140,8246,382],{"class":146},[140,8248,8249],{"class":164},"\u003Ch1>Witaj!\u003C\u002Fh1>\u003Cp>To jest \u003Cstrong>HTML\u003C\u002Fstrong> email.\u003C\u002Fp>",[140,8251,1081],{"class":146},[140,8253,3938],{"class":146},[140,8255,8256,8259,8261,8263,8265],{"class":142,"line":6658},[140,8257,8258],{"class":150},"  bodyType",[140,8260,494],{"class":146},[140,8262,382],{"class":146},[140,8264,6110],{"class":164},[140,8266,388],{"class":146},[140,8268,8269,8271],{"class":142,"line":6663},[140,8270,1693],{"class":146},[140,8272,1101],{"class":556},[140,8274,8275],{"class":142,"line":6678},[140,8276,7855],{"class":164},[140,8278,8279],{"class":142,"line":6702},[140,8280,574],{"emptyLinePlaceholder":10},[140,8282,8283,8285],{"class":142,"line":6709},[140,8284,7447],{"class":146},[140,8286,8287],{"class":363},"Email z załącznikami\n",[140,8289,8290],{"class":142,"line":6721},[140,8291,574],{"emptyLinePlaceholder":10},[140,8293,8294,8296],{"class":142,"line":6737},[140,8295,7818],{"class":164},[140,8297,7968],{"class":7367},[140,8299,8300,8302,8304,8306,8308,8310],{"class":142,"line":6752},[140,8301,8199],{"class":585},[140,8303,8029],{"class":556},[140,8305,56],{"class":146},[140,8307,4828],{"class":1038},[140,8309,1053],{"class":556},[140,8311,3887],{"class":146},[140,8313,8314,8316,8318,8320,8322,8324],{"class":142,"line":6757},[140,8315,8042],{"class":150},[140,8317,494],{"class":146},[140,8319,382],{"class":146},[140,8321,8049],{"class":164},[140,8323,1081],{"class":146},[140,8325,3938],{"class":146},[140,8327,8328,8330,8332,8334,8337,8339],{"class":142,"line":6762},[140,8329,8058],{"class":150},[140,8331,494],{"class":146},[140,8333,382],{"class":146},[140,8335,8336],{"class":164},"Email z załącznikiem",[140,8338,1081],{"class":146},[140,8340,3938],{"class":146},[140,8342,8343,8345,8347,8349,8352,8354],{"class":142,"line":6767},[140,8344,8074],{"class":150},[140,8346,494],{"class":146},[140,8348,382],{"class":146},[140,8350,8351],{"class":164},"Zobacz załącznik w załączniku",[140,8353,1081],{"class":146},[140,8355,3938],{"class":146},[140,8357,8358,8361,8363],{"class":142,"line":6772},[140,8359,8360],{"class":150},"  attachments",[140,8362,494],{"class":146},[140,8364,1613],{"class":556},[140,8366,8367],{"class":142,"line":6777},[140,8368,8369],{"class":146},"    {\n",[140,8371,8372,8375,8377,8379,8382,8384],{"class":142,"line":6783},[140,8373,8374],{"class":150},"      name",[140,8376,494],{"class":146},[140,8378,382],{"class":146},[140,8380,8381],{"class":164},"dokument.pdf",[140,8383,1081],{"class":146},[140,8385,3938],{"class":146},[140,8387,8388,8391,8393,8395,8398,8400],{"class":142,"line":6802},[140,8389,8390],{"class":150},"      contentBytes",[140,8392,494],{"class":146},[140,8394,382],{"class":146},[140,8396,8397],{"class":164},"base64_encoded_file_content",[140,8399,1081],{"class":146},[140,8401,3938],{"class":146},[140,8403,8404,8407,8409,8411,8414],{"class":142,"line":6807},[140,8405,8406],{"class":150},"      contentType",[140,8408,494],{"class":146},[140,8410,382],{"class":146},[140,8412,8413],{"class":164},"application\u002Fpdf",[140,8415,388],{"class":146},[140,8417,8418],{"class":142,"line":6819},[140,8419,4074],{"class":146},[140,8421,8422],{"class":142,"line":6826},[140,8423,8424],{"class":556},"  ]\n",[140,8426,8427,8429],{"class":142,"line":6842},[140,8428,1693],{"class":146},[140,8430,1101],{"class":556},[140,8432,8433],{"class":142,"line":6854},[140,8434,7855],{"class":164},[140,8436,8437],{"class":142,"line":6859},[140,8438,574],{"emptyLinePlaceholder":10},[140,8440,8441,8443],{"class":142,"line":6864},[140,8442,7447],{"class":146},[140,8444,8445],{"class":363},"Test połączenia\n",[140,8447,8448],{"class":142,"line":6870},[140,8449,574],{"emptyLinePlaceholder":10},[140,8451,8452,8454],{"class":142,"line":6891},[140,8453,7818],{"class":164},[140,8455,7968],{"class":7367},[140,8457,8458,8460,8463,8465,8467,8469,8471,8474],{"class":142,"line":6917},[140,8459,1027],{"class":154},[140,8461,8462],{"class":556}," isConnected ",[140,8464,158],{"class":146},[140,8466,1035],{"class":585},[140,8468,8029],{"class":556},[140,8470,56],{"class":146},[140,8472,8473],{"class":1038},"testConnection",[140,8475,1111],{"class":556},[140,8477,8478,8480,8483],{"class":142,"line":6938},[140,8479,8098],{"class":585},[140,8481,8482],{"class":556}," (isConnected) ",[140,8484,3887],{"class":146},[140,8486,8487,8489,8491,8493,8495,8497,8500,8502],{"class":142,"line":6947},[140,8488,8113],{"class":556},[140,8490,56],{"class":146},[140,8492,6591],{"class":1038},[140,8494,1053],{"class":150},[140,8496,1081],{"class":146},[140,8498,8499],{"class":164},"Połączenie z MS Graph API działa!",[140,8501,1081],{"class":146},[140,8503,1101],{"class":150},[140,8505,8506],{"class":142,"line":6957},[140,8507,4084],{"class":146},[140,8509,8510],{"class":142,"line":6977},[140,8511,7855],{"class":164},[140,8513,8514],{"class":142,"line":6985},[140,8515,574],{"emptyLinePlaceholder":10},[140,8517,8518,8520,8522,8524,8526],{"class":142,"line":6990},[140,8519,7652],{"class":7651},[140,8521,7310],{"class":560},[140,8523,7657],{"class":564},[140,8525,561],{"class":560},[140,8527,8528],{"class":585}," Test połączenia sprawdza czy można uzyskać token dostępu z Azure AD. Nie wymaga dodatkowych uprawnień API.\n",[140,8530,8531],{"class":142,"line":7005},[140,8532,574],{"emptyLinePlaceholder":10},[140,8534,8535,8537],{"class":142,"line":7029},[140,8536,4905],{"class":146},[140,8538,8539],{"class":363},"🏃 Uruchomienie testów\n",[140,8541,8542],{"class":142,"line":7036},[140,8543,574],{"emptyLinePlaceholder":10},[140,8545,8546,8548],{"class":142,"line":7041},[140,8547,7818],{"class":164},[140,8549,7368],{"class":7367},[140,8551,8552],{"class":142,"line":7046},[140,8553,8554],{"class":1650},"# Uruchomienie testów\n",[140,8556,8558,8561],{"class":142,"line":8557},160,[140,8559,8560],{"class":363},"npm",[140,8562,8563],{"class":164}," start\n",[140,8565,8567],{"class":142,"line":8566},161,[140,8568,574],{"emptyLinePlaceholder":10},[140,8570,8572],{"class":142,"line":8571},162,[140,8573,8574],{"class":1650},"# lub bezpośrednio\n",[140,8576,8578,8580],{"class":142,"line":8577},163,[140,8579,1750],{"class":363},[140,8581,8582],{"class":164}," main.js\n",[140,8584,8586],{"class":142,"line":8585},164,[140,8587,7855],{"class":164},[140,8589,8591],{"class":142,"line":8590},165,[140,8592,574],{"emptyLinePlaceholder":10},[140,8594,8596,8598],{"class":142,"line":8595},166,[140,8597,4905],{"class":146},[140,8599,8600],{"class":363},"📁 Struktura projektu\n",[140,8602,8604],{"class":142,"line":8603},167,[140,8605,574],{"emptyLinePlaceholder":10},[140,8607,8609],{"class":142,"line":8608},168,[140,8610,7855],{"class":164},[140,8612,8614],{"class":142,"line":8613},169,[140,8615,8616],{"class":7367},"exchange-mailbox-nodejs\u002F\n",[140,8618,8620],{"class":142,"line":8619},170,[140,8621,8622],{"class":7367},"├── 📄 ExchangeMailbox.js    # Główna klasa do wysyłania emaili\n",[140,8624,8626],{"class":142,"line":8625},171,[140,8627,8628],{"class":7367},"├── 📄 main.js              # Testy i przykłady użycia\n",[140,8630,8632],{"class":142,"line":8631},172,[140,8633,8634],{"class":7367},"├── 📄 package.json         # Konfiguracja NPM i zależności\n",[140,8636,8638],{"class":142,"line":8637},173,[140,8639,8640],{"class":7367},"├── 📄 .env                 # Zmienne środowiskowe (konfiguracja)\n",[140,8642,8644],{"class":142,"line":8643},174,[140,8645,8646],{"class":7367},"├── 📄 .gitignore          # Wykluczenia Git (chroni .env)\n",[140,8648,8650],{"class":142,"line":8649},175,[140,8651,8652],{"class":7367},"├── 📄 README.md           # Dokumentacja (ten plik)\n",[140,8654,8656],{"class":142,"line":8655},176,[140,8657,8658],{"class":7367},"└── 📁 .github\u002F\n",[140,8660,8662],{"class":142,"line":8661},177,[140,8663,8664],{"class":7367},"    └── 📄 copilot-instructions.md  # Instrukcje dla Copilot\n",[140,8666,8668],{"class":142,"line":8667},178,[140,8669,7855],{"class":164},[140,8671,8673],{"class":142,"line":8672},179,[140,8674,574],{"emptyLinePlaceholder":10},[140,8676,8678,8680],{"class":142,"line":8677},180,[140,8679,4905],{"class":146},[140,8681,8682],{"class":363},"🔍 Rozwiązywanie problemów\n",[140,8684,8686],{"class":142,"line":8685},181,[140,8687,574],{"emptyLinePlaceholder":10},[140,8689,8691,8693],{"class":142,"line":8690},182,[140,8692,7447],{"class":146},[140,8694,8695],{"class":363},"Błędy uwierzytelniania\n",[140,8697,8699],{"class":142,"line":8698},183,[140,8700,574],{"emptyLinePlaceholder":10},[140,8702,8704,8706,8708,8711,8713],{"class":142,"line":8703},184,[140,8705,1263],{"class":146},[140,8707,7310],{"class":560},[140,8709,8710],{"class":564},"\"Invalid client credentials\"",[140,8712,561],{"class":560},[140,8714,8715],{"class":556}," - sprawdź Client ID i Client Secret w pliku .env\n",[140,8717,8719,8721,8723,8726,8728],{"class":142,"line":8718},185,[140,8720,1280],{"class":146},[140,8722,7310],{"class":560},[140,8724,8725],{"class":564},"\"Invalid tenant\"",[140,8727,561],{"class":560},[140,8729,8730],{"class":556}," - sprawdź Tenant ID w pliku .env\n",[140,8732,8734,8736,8738,8741,8743,8746,8748,8750,8752],{"class":142,"line":8733},186,[140,8735,4982],{"class":146},[140,8737,7310],{"class":560},[140,8739,8740],{"class":564},"\"Insufficient privileges\"",[140,8742,561],{"class":560},[140,8744,8745],{"class":556}," - upewnij się że aplikacja ma uprawnienie ",[140,8747,598],{"class":146},[140,8749,7621],{"class":164},[140,8751,598],{"class":146},[140,8753,8754],{"class":556}," i zostało zatwierdzone przez administratora\n",[140,8756,8758],{"class":142,"line":8757},187,[140,8759,574],{"emptyLinePlaceholder":10},[140,8761,8763,8765],{"class":142,"line":8762},188,[140,8764,7447],{"class":146},[140,8766,8767],{"class":363},"Błędy wysyłania emaili\n",[140,8769,8771],{"class":142,"line":8770},189,[140,8772,574],{"emptyLinePlaceholder":10},[140,8774,8776,8778,8780,8783,8785],{"class":142,"line":8775},190,[140,8777,1263],{"class":146},[140,8779,7310],{"class":560},[140,8781,8782],{"class":564},"\"User not found\"",[140,8784,561],{"class":560},[140,8786,8787],{"class":556}," - sprawdź czy SENDER_EMAIL to prawdziwy użytkownik w organizacji\n",[140,8789,8791,8793,8795,8798,8800],{"class":142,"line":8790},191,[140,8792,1280],{"class":146},[140,8794,7310],{"class":560},[140,8796,8797],{"class":564},"\"Access denied\"",[140,8799,561],{"class":560},[140,8801,8802],{"class":556}," - sprawdź czy admin zatwierdził uprawnienia aplikacji\n",[140,8804,8806,8808,8810,8813,8815],{"class":142,"line":8805},192,[140,8807,4982],{"class":146},[140,8809,7310],{"class":560},[140,8811,8812],{"class":564},"\"Mailbox not found\"",[140,8814,561],{"class":560},[140,8816,8817],{"class":556}," - upewnij się że użytkownik ma licencję Exchange Online\n",[140,8819,8821],{"class":142,"line":8820},193,[140,8822,574],{"emptyLinePlaceholder":10},[140,8824,8826,8828],{"class":142,"line":8825},194,[140,8827,7447],{"class":146},[140,8829,8830],{"class":363},"Częste problemy podczas testów\n",[140,8832,8834],{"class":142,"line":8833},195,[140,8835,574],{"emptyLinePlaceholder":10},[140,8837,8839,8841,8843,8846,8848],{"class":142,"line":8838},196,[140,8840,1263],{"class":146},[140,8842,7310],{"class":560},[140,8844,8845],{"class":564},"\"\u002Fme request is only valid with delegated authentication flow\"",[140,8847,561],{"class":560},[140,8849,8850],{"class":556}," - Ten błąd jest normalny, używamy uwierzytelniania aplikacji (client credentials)\n",[140,8852,8854,8856,8858,8861,8863],{"class":142,"line":8853},197,[140,8855,1280],{"class":146},[140,8857,7310],{"class":560},[140,8859,8860],{"class":564},"\"Insufficient privileges to complete the operation\"",[140,8862,561],{"class":560},[140,8864,8865],{"class":556}," - Nie jest potrzebne uprawnienie User.Read dla podstawowej funkcjonalności wysyłania emaili\n",[140,8867,8869,8871,8873,8876,8880,8883,8885],{"class":142,"line":8868},198,[140,8870,4982],{"class":146},[140,8872,7310],{"class":560},[140,8874,8875],{"class":564},"\"TypeError: ",[140,8877,8879],{"class":8878},"synWL","\\_",[140,8881,8882],{"class":564},"this.provider is not a function\"",[140,8884,561],{"class":560},[140,8886,8887],{"class":556}," - Problem z konfiguracją klienta Graph, rozwiązany w aktualnej wersji\n",[140,8889,8891],{"class":142,"line":8890},199,[140,8892,574],{"emptyLinePlaceholder":10},[140,8894,8896,8898],{"class":142,"line":8895},200,[140,8897,7447],{"class":146},[140,8899,8900],{"class":363},"Debugging\n",[140,8902,8904],{"class":142,"line":8903},201,[140,8905,574],{"emptyLinePlaceholder":10},[140,8907,8909,8911],{"class":142,"line":8908},202,[140,8910,579],{"class":146},[140,8912,8913],{"class":556}," Test połączenia sprawdza czy można uzyskać token dostępu (nie wymaga dodatkowych uprawnień)\n",[140,8915,8917,8919],{"class":142,"line":8916},203,[140,8918,579],{"class":146},[140,8920,8921],{"class":556}," Włącz szczegółowe logi błędów w konsoli aby zobaczyć dokładne komunikaty z MS Graph API\n",[140,8923,8925,8927],{"class":142,"line":8924},204,[140,8926,579],{"class":146},[140,8928,8929],{"class":556}," Sprawdź czy wszystkie zmienne środowiskowe w .env są wypełnione\n",[140,8931,8933],{"class":142,"line":8932},205,[140,8934,574],{"emptyLinePlaceholder":10},[140,8936,8938,8940],{"class":142,"line":8937},206,[140,8939,4905],{"class":146},[140,8941,8942],{"class":363},"📦 Zależności\n",[140,8944,8946],{"class":142,"line":8945},207,[140,8947,574],{"emptyLinePlaceholder":10},[140,8949,8951,8953,8955,8957,8959],{"class":142,"line":8950},208,[140,8952,579],{"class":146},[140,8954,5320],{"class":146},[140,8956,5144],{"class":164},[140,8958,598],{"class":146},[140,8960,8961],{"class":556}," - Uwierzytelnianie Microsoft Authentication Library\n",[140,8963,8965,8967,8969,8971,8973],{"class":142,"line":8964},209,[140,8966,579],{"class":146},[140,8968,5320],{"class":146},[140,8970,5164],{"class":164},[140,8972,598],{"class":146},[140,8974,8975],{"class":556}," - Klient MS Graph API\n",[140,8977,8979,8981,8983,8985,8987],{"class":142,"line":8978},210,[140,8980,579],{"class":146},[140,8982,5320],{"class":146},[140,8984,5181],{"class":164},[140,8986,598],{"class":146},[140,8988,8989],{"class":556}," - Ładowanie zmiennych środowiskowych\n",[140,8991,8993],{"class":142,"line":8992},211,[140,8994,574],{"emptyLinePlaceholder":10},[140,8996,8998,9000],{"class":142,"line":8997},212,[140,8999,4905],{"class":146},[140,9001,9002],{"class":363},"📄 Licencja\n",[140,9004,9006],{"class":142,"line":9005},213,[140,9007,574],{"emptyLinePlaceholder":10},[140,9009,9011],{"class":142,"line":9010},214,[140,9012,9013],{"class":556},"MIT License - możesz swobodnie używać, modyfikować i dystrybuować ten kod.\n",[140,9015,9017],{"class":142,"line":9016},215,[140,9018,574],{"emptyLinePlaceholder":10},[140,9020,9022,9024],{"class":142,"line":9021},216,[140,9023,4905],{"class":146},[140,9025,9026],{"class":363},"🆘 Pomoc\n",[140,9028,9030],{"class":142,"line":9029},217,[140,9031,574],{"emptyLinePlaceholder":10},[140,9033,9035],{"class":142,"line":9034},218,[140,9036,9037],{"class":556},"Jeśli napotkasz problemy:\n",[140,9039,9041],{"class":142,"line":9040},219,[140,9042,574],{"emptyLinePlaceholder":10},[140,9044,9046,9048],{"class":142,"line":9045},220,[140,9047,1263],{"class":146},[140,9049,9050],{"class":556}," Sprawdź konfigurację w Azure Portal\n",[140,9052,9054,9056,9059,9061,9063],{"class":142,"line":9053},221,[140,9055,1280],{"class":146},[140,9057,9058],{"class":556}," Zweryfikuj zmienne środowiskowe w ",[140,9060,598],{"class":146},[140,9062,783],{"class":164},[140,9064,4970],{"class":146},[140,9066,9068,9070,9073,9075,9078],{"class":142,"line":9067},222,[140,9069,4982],{"class":146},[140,9071,9072],{"class":556}," Uruchom test połączenia: ",[140,9074,598],{"class":146},[140,9076,9077],{"class":164},"await mailbox.testConnection()",[140,9079,4970],{"class":146},[140,9081,9083,9085],{"class":142,"line":9082},223,[140,9084,7405],{"class":146},[140,9086,9087],{"class":556}," Sprawdź logi błędów w konsoli\n",[140,9089,9091],{"class":142,"line":9090},224,[140,9092,574],{"emptyLinePlaceholder":10},[140,9094,9096],{"class":142,"line":9095},225,[140,9097,9098],{"class":556},"---\n",[140,9100,9102],{"class":142,"line":9101},226,[140,9103,574],{"emptyLinePlaceholder":10},[140,9105,9107,9109,9112],{"class":142,"line":9106},227,[140,9108,561],{"class":560},[140,9110,9111],{"class":564},"📧 Miłego wysyłania emaili z ExchangeMailbox! 🚀",[140,9113,7222],{"class":560},[9115,9116,9119],"code-preview",{"label":9117,"icon":9118},"Podgląd","i-lucide-eye",[31,9120,9121,9127,9130],{},[41,9122,9123],{},[35,9124,9126],{"href":9125},"\u002Fprevs\u002Fp0001","Podglądu pliku README.md",[9128,9129],"br",{},[9128,9131],{},[26,9133,2025],{"id":2024},[31,9135,9136,9137,9139,9140,52,9143,9146],{},"Wygenerowany kod nie jest idealny. Klasa ",[41,9138,4814],{}," może mieć zastosowanie w różnych projektach i nie powinna używać ",[61,9141,9142],{},"console.log()",[61,9144,9145],{},"console.error()",", tylko generować wyjątki w przypadku błędów krytycznych. Wyjątki można \"złapać\" w obszarach brzegowych kodu, na przykład na styku z użytkownikiem końcowym i tam wyświetlić czytelny komunikat.",[31,9148,9149,9150,9152],{},"Takie rzeczy da się oczywiście poprawić i warto to robić z użyciem agenta, który działając w już zainicjowanym kontekście, będzie trzymał się swojego stylu pracy. Na przykład: jeśli stworzył plik ",[41,9151,3389],{},", będzie go na bieżąco aktualizował.",[31,9154,9155,9156,56,9159,9161],{},"Po około 15 minutach pracy z agentem miałem już działającą metodę ",[61,9157,9158],{},"sendMail()",[9128,9160],{},"Działam dalej...",[31,9163,9164,9165,9167,9168,9171,9172,9174,9175,52,9177,9179],{},"W pliku ",[41,9166,4872],{}," dodałem sekcję ",[61,9169,9170],{},"## Rules"," instruując agenta, żeby w metodach klasy ",[41,9173,4814],{}," nie używał ",[61,9176,9142],{},[61,9178,9145],{},". Napisałem kilka prompt-ów i po kilku minutach miałem gotowe metody do:",[417,9181,9182,9185,9188,9191],{},[420,9183,9184],{},"pobierania listy nieprzeczytanych wiadomości",[420,9186,9187],{},"odczytywania treści wybranej wiadomości",[420,9189,9190],{},"pobierania i zapisywania na dysku załączników z wybranej wiadomości",[420,9192,9193],{},"oznaczania i odznaczania wiadomości jako przeczytanej",[31,9195,9196],{},"Nie ma wątpliwości, że przyspieszenie pracy jest kilkukrotne, może nawet kilkunastokrotne.",[31,9198,9199,9200,9203],{},"Na ten moment nie przekonuje mnie ",[285,9201,9202],{},"vibe coding"," w stylu generowania dużej ilości kodu na raz, na przykład całej aplikacji lub wielu funkcjonalności w jednym podejściu. Praca z agentem, który w danym momencie wykonuje konkretną, dobrze opisaną pracę, daje nadzorcy możliwość szybkiej weryfikacji zarówno jakości, jak i poprawności kodu.",[2065,9205,9206],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .s6hCs, html code.shiki .s6hCs{--shiki-light:#9C3EDA;--shiki-light-font-style:italic;--shiki-default:#C792EA;--shiki-default-font-style:italic;--shiki-dark:#C792EA;--shiki-dark-font-style:italic}html pre.shiki code .sFweD, html code.shiki .sFweD{--shiki-light:#E2931D;--shiki-light-font-style:italic;--shiki-default:#FFCB6B;--shiki-default-font-style:italic;--shiki-dark:#FFCB6B;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sHepR, html code.shiki .sHepR{--shiki-light:#39ADB5;--shiki-light-font-weight:bold;--shiki-default:#89DDFF;--shiki-default-font-weight:bold;--shiki-dark:#89DDFF;--shiki-dark-font-weight:bold}html pre.shiki code .so75L, html code.shiki .so75L{--shiki-light:#E53935;--shiki-light-font-weight:bold;--shiki-default:#F07178;--shiki-default-font-weight:bold;--shiki-dark:#F07178;--shiki-dark-font-weight:bold}html pre.shiki code .sJsPd, html code.shiki .sJsPd{--shiki-light:#90A4AE90;--shiki-default:#EEFFFF90;--shiki-dark:#BABED890}html pre.shiki code .sDpXG, html code.shiki .sDpXG{--shiki-light:#E53935;--shiki-light-text-decoration:underline;--shiki-default:#F07178;--shiki-default-text-decoration:underline;--shiki-dark:#F07178;--shiki-dark-text-decoration:underline}html pre.shiki code .sCNwf, html code.shiki .sCNwf{--shiki-light:#FF5370;--shiki-light-font-style:italic;--shiki-default:#FF9CAC;--shiki-default-font-style:italic;--shiki-dark:#FF9CAC;--shiki-dark-font-style:italic}html pre.shiki code .synWL, html code.shiki .synWL{--shiki-light:#90A4AE;--shiki-light-font-weight:bold;--shiki-default:#EEFFFF;--shiki-default-font-weight:bold;--shiki-dark:#BABED8;--shiki-dark-font-weight:bold}",{"title":136,"searchDepth":185,"depth":185,"links":9208},[9209,9210,9211,9212],{"id":28,"depth":185,"text":29},{"id":4774,"depth":185,"text":4775},{"id":4860,"depth":185,"text":4861},{"id":2024,"depth":185,"text":2025},"2025-10-28","GitHub Copilot pisze za mnie klasę do obsługi skrzynki pocztowej MS Exchange","\u002Fpics\u002F0002.png",{"order":185},"\u002Fblog\u002Fai-for-coding",{"title":4707,"description":9214},{"loc":9217},"blog\u002Fai-for-coding","scYd8hc6iwH2QJNPt9CodnDe3V14YDuyXBYvxFRT2AQ",{"id":9223,"title":9224,"author":21,"body":9225,"date":9530,"description":9531,"draft":2088,"extension":2089,"image":9532,"meta":9533,"minRead":6,"navigation":10,"path":9534,"seo":9535,"sitemap":9536,"stem":9537,"__hash__":9538},"blog\u002Fblog\u002Fjak-powstala-ta-strona.md","Jak powstała ta strona",{"type":23,"value":9226,"toc":9522},[9227,9231,9259,9267,9270,9283,9293,9297,9311,9344,9348,9365,9368,9413,9417,9447,9457,9461,9480,9505,9507,9515],[26,9228,9230],{"id":9229},"vercel-łączy-się-z-nuxtlabs","Vercel łączy się z NuxtLabs",[31,9232,9233,9234,52,9240,9246,9247,9250,9251,9258],{},"Od jakiegoś czasu śledzę zmiany w ekosystemie ",[41,9235,9236],{},[35,9237,2892],{"href":9238,"rel":9239},"https:\u002F\u002Fnuxt.com",[111],[41,9241,9242],{},[35,9243,2848],{"href":9244,"rel":9245},"https:\u002F\u002Fvuejs.org",[111],", a ostatnia z nich jest dla mnie szczególnie istotna — firma ",[41,9248,9249],{},"NuxtLabs"," oficjalnie połączyła siły z ",[41,9252,9253],{},[35,9254,9257],{"href":9255,"rel":9256},"https:\u002F\u002Fvercel.com",[111],"Vercel",". To połączenie otwiera nowy rozdział w rozwoju Nuxt i jego ekosystemu.",[31,9260,9261,9262],{},"Oficjalne ogłoszenie: ",[35,9263,9266],{"href":9264,"rel":9265},"https:\u002F\u002Fvercel.com\u002Fblog\u002Fnuxtlabs-joins-vercel",[111],"Vercel Blog – NuxtLabs joins Vercel",[31,9268,9269],{},"Z perspektywy deweloperów oznacza to:",[417,9271,9272,9275],{},[420,9273,9274],{},"jeszcze lepsze wsparcie infrastrukturalne (hosting, edge, CI\u002FCD),",[420,9276,9277,9278,52,9280,56],{},"większe inwestycje w rozwój ",[41,9279,2892],{},[41,9281,9282],{},"Nitro",[31,9284,9285,9286,9289,9290,56],{},"Co ważne, projekty pozostają ",[41,9287,9288],{},"open source"," na licencji ",[41,9291,9292],{},"MIT",[26,9294,9296],{"id":9295},"nuxt-ui-pro-dostępne-za-darmo","Nuxt UI Pro dostępne za darmo",[31,9298,9299,9300,9307,9308,9310],{},"Wraz z połączeniem pojawiła się zapowiedź udostępnienia ",[41,9301,9302],{},[35,9303,9306],{"href":9304,"rel":9305},"https:\u002F\u002Fui.nuxt.com",[111],"Nuxt UI Pro"," całkowicie za darmo. Dotychczas biblioteka była dostępna w modelu subskrypcyjnym, teraz została włączona do jednej, zunifikowanej biblioteki ",[41,9309,3844],{},", która oferuje m.in.:",[417,9312,9313,9320,9328,9335],{},[420,9314,9315,9316,9319],{},"ponad ",[41,9317,9318],{},"100 gotowych komponentów"," UI,",[420,9321,9322,9323,52,9325,1060],{},"pełną integrację z ",[41,9324,2896],{},[41,9326,9327],{},"ARIA",[420,9329,9330,9331,9334],{},"darmowy ",[41,9332,9333],{},"Figma Kit"," do projektowania interfejsów,",[420,9336,9337,9338,52,9341,56],{},"pełną zgodność z ",[41,9339,9340],{},"Vue 3",[41,9342,9343],{},"Nuxt 3\u002F4",[26,9345,9347],{"id":9346},"szablony-nuxt-ui-szybki-start","Szablony Nuxt UI — szybki start",[31,9349,9350,9351,9353,9354,9361,9362,9364],{},"Zespół ",[41,9352,2892],{}," udostępnił zestaw ",[41,9355,9356],{},[35,9357,9360],{"href":9358,"rel":9359},"https:\u002F\u002Fui.nuxt.com\u002Ftemplates",[111],"gotowych szablonów"," opartych o ",[41,9363,3844],{},". Dzięki nim można w kilka minut rozpocząć budowę strony lub aplikacji.",[31,9366,9367],{},"Dostępne są m.in.:",[417,9369,9370,9380,9390],{},[420,9371,9372,9379],{},[41,9373,9374],{},[35,9375,9378],{"href":9376,"rel":9377},"https:\u002F\u002Fstarter-template.nuxt.dev\u002F",[111],"Starter"," – lekki szablon bazowy,",[420,9381,9382,9389],{},[41,9383,9384],{},[35,9385,9388],{"href":9386,"rel":9387},"https:\u002F\u002Fportfolio-template.nuxt.dev\u002F",[111],"Portfolio"," – idealny dla twórców i freelancerów,",[420,9391,9392,64,9399,64,9405,9412],{},[41,9393,9394],{},[35,9395,9398],{"href":9396,"rel":9397},"https:\u002F\u002Fdocs-template.nuxt.dev\u002F",[111],"Docs",[41,9400,9401],{},[35,9402,3706],{"href":9403,"rel":9404},"https:\u002F\u002Fsaas-template.nuxt.dev\u002F",[111],[41,9406,9407],{},[35,9408,9411],{"href":9409,"rel":9410},"https:\u002F\u002Fdashboard-template.nuxt.dev\u002F",[111],"Dashboard"," i inne.",[26,9414,9416],{"id":9415},"jak-powstała-ta-strona","Jak powstała ta strona?",[31,9418,9419,9420,9425,9426,9430,9431,9436,9437,9444,9445,56],{},"Stronę zbudowałem na bazie szablonu ",[41,9421,9422],{},[35,9423,9378],{"href":9376,"rel":9424},[111],", a część obsługującą ",[41,9427,9428],{},[35,9429,15],{"href":11}," zapożyczyłem z ",[41,9432,9433],{},[35,9434,9388],{"href":9386,"rel":9435},[111],". Ta dodatkowa część bazuje na module ",[41,9438,9439],{},[35,9440,9443],{"href":9441,"rel":9442},"https:\u002F\u002Fcontent.nuxt.com\u002F",[111],"Nuxt Content"," — jednym z wielu modułów oficjalnie dostępnych dla ",[41,9446,2892],{},[31,9448,9449,9450,56],{},"Oficjalna strona z modułami: ",[41,9451,9452],{},[35,9453,9456],{"href":9454,"rel":9455},"https:\u002F\u002Fnuxt.com\u002Fmodules",[111],"Nuxt Modules",[26,9458,9460],{"id":9459},"dlaczego-ekosystem-vue-nuxt-nuxt-ui","Dlaczego ekosystem Vue + Nuxt + Nuxt UI?",[31,9462,9463,9464,64,9469,52,9474,9479],{},"Połączenie ",[41,9465,9466],{},[35,9467,2848],{"href":9244,"rel":9468},[111],[41,9470,9471],{},[35,9472,2892],{"href":9238,"rel":9473},[111],[41,9475,9476],{},[35,9477,3844],{"href":9304,"rel":9478},[111]," to dziś jeden z najbardziej elastycznych i wydajnych stacków frontendowych:",[417,9481,9482,9494,9497,9502],{},[420,9483,9484,64,9487,52,9490,9493],{},[41,9485,9486],{},"SSR",[41,9488,9489],{},"SSG",[41,9491,9492],{},"ISR"," dostępne „z pudełka”,",[420,9495,9496],{},"nowoczesny system komponentów i motywów,",[420,9498,9499,9500,1060],{},"prosty dostęp do treści z ",[41,9501,9443],{},[420,9503,9504],{},"świetna dokumentacja oraz wsparcie społeczności.",[26,9506,2025],{"id":2024},[31,9508,9509,9510,406,9512,9514],{},"Decyzja o połączeniu ",[41,9511,9249],{},[41,9513,9257],{}," sprawiła, że narzędzia, których do tej pory używałem komercyjnie, mam teraz w pełni za darmo — i to bez kompromisów licencyjnych. Postawienie tej strony zajęło mi znacznie mniej czasu niż gdybym składał ją od zera: gotowy szablon, działająca obsługa bloga i spójny zestaw komponentów UI to bardzo solidny punkt startu.",[31,9516,9517,9518,9521],{},"Stack ",[41,9519,9520],{},"Vue + Nuxt + Nuxt UI"," zostaje u mnie na dłużej — zarówno w projektach prywatnych, jak i komercyjnych.",{"title":136,"searchDepth":185,"depth":185,"links":9523},[9524,9525,9526,9527,9528,9529],{"id":9229,"depth":185,"text":9230},{"id":9295,"depth":185,"text":9296},{"id":9346,"depth":185,"text":9347},{"id":9415,"depth":185,"text":9416},{"id":9459,"depth":185,"text":9460},{"id":2024,"depth":185,"text":2025},"2025-10-27","Postawiłem tę stronę na świeżo udostępnionym za darmo Nuxt UI Pro.","\u002Fpics\u002F0001.png",{"order":143},"\u002Fblog\u002Fjak-powstala-ta-strona",{"title":9224,"description":9531},{"loc":9534},"blog\u002Fjak-powstala-ta-strona","__O0V9xVE7sH9Lsqthp8DgnX_eq0B49cn-0Ay9EUY6o",1780996674662]