Sesam.hu — Engineering Manager & Trail Runner

Sesam.hu

Personal blog by Péter Szilágyi — Engineering Manager and trail runner based in Stockholm.
Writing on leadership, running, and life in the north.

English

The day started with me noticing an innocent-looking option in Lovable's project settings: Migrate to TanStack Start (Advanced). It turned out that, since May 13, 2026, newly created projects have used this stack, which supports server-side rendering (SSR), instead of the original Vite + React Router single-page application setup. Lovable has an information page explaining the differences.

Migration to TanStack Start

I first asked about the implications in Chat mode, and the migration looked like a good idea. It also brought several stack upgrades: React 18 → 19, Tailwind CSS 3 → 4, and the replacement of react-helmet-async with React 19's native document metadata support in SEO. The only backend function affected was admin-upload-image, which handles image uploads in the post editor and was converted into an in-app server function.

After the migration, I immediately noticed that post images flickered when I opened a post. The cause was that the post page still followed the original single-page application logic instead of using TanStack Start's native patterns. The solution was to have the route loader prefetch the post and its previous/next links, then pass the data to the page, which reads it using useSuspenseQuery. At the same time, loading and error states were moved to the route's pendingComponent and errorComponent, respectively, and the Markdown plugins and component maps were moved to module-level constants to prevent images from being remounted on every render. Boom, no more flickering on load.

Next, I set out to adopt native TanStack patterns across the site. The home, language, search, and admin pages now load their data in route loaders, with skeletons and error states handled by route-level pending and error components. Page, query, and admin-tab state is managed through typed URL search parameters. Hovering over a link preloads the destination page.

🚀 I have to say, the whole site became a rocket ship, with pages loading almost instantly.

After a full audit, I was able to remove the router compatibility shim that preserved the original paths, the legacy redirect components, the /404 route, the duplicate shadcn toast system (leaving sonner as the sole implementation), a dozen other unused files, and temporary files left over from the migration. Switching to useSuspenseQuery also made some hooks obsolete, so I deleted those, too. Finally, I removed the orphaned @radix-ui/react-toast and @radix-ui/react-toggle dependencies, which were no longer needed after their corresponding UI components had been removed.

I also discovered that a second, divergent Supabase browser client had been configured despite having no importers. Unlike the single SSR-safe @/lib/supabaseClient used by the application, this duplicate client had unguarded session persistence. I removed it along with its now-orphaned previewAuthStorage.ts helper, leaving the SSR-safe client as the only browser client in use.

However, Lovable's infrastructure regenerates src/integrations/supabase/client.ts whenever I ask it to make certain changes, even though the file is unused and comes with a comment telling me not to touch it. Since preventing its regeneration isn't an option, I added a lint guard using no-restricted-imports. If any code accidentally imports the generated client, the build now fails.

Social images

One major benefit I hadn't initially anticipated was that migrating to TanStack Start finally made per-post social share images possible. With the original Vite + React Router single-page setup, I couldn't provide a post-specific og:image in the initial HTML. Now, each post route can set its own Open Graph metadata through head(), allowing social platforms such as Bluesky, Facebook, and X to discover the post's first image when fetching the page and use it for the link preview.

The preview image is prepared at 1200 × 630 pixels, following common social-sharing guidelines. Posts without images fall back to the generic site banner.

To accommodate WhatsApp and other picky clients, I also added compressed social preview images. The preview endpoint serves a JPEG under 1 MB, using the following approach:

  • JPEG source images already under 1 MB are returned unchanged.
  • Other images are resized to the required dimensions and encoded as JPEG, with transparency flattened onto a white background for PNG sources.
  • If the encoded image exceeds the byte limit, the encoder retries at progressively lower quality settings: 75, 60, 45, and 30.
  • If the image still exceeds the limit after all four attempts, the endpoint rejects it rather than serving an oversized preview. When a post's image cannot be processed, the route falls back to the generic site banner.

The endpoint also enforces source-size and pixel limits, so it doesn't have to process the original full-resolution uploads. The result is a strict size limit for every preview image served, without sacrificing the original post photos.

With each route now setting its own metadata through head(), the SEO component was no longer needed and could be removed.

Shared Markdown renderer

I hadn't realised that the admin dashboard's preview and the live post display used separate component maps for Markdown rendering. The two implementations had already started to drift apart, meaning the preview no longer accurately reflected what the published post would look like.

To fix this, I moved the ReactMarkdown component maps and plugin arrays into a shared component that both the preview and the live post display now use. This also made the duplicated, roughly 140-line component map in the editor obsolete.

Prettier formatting baseline

While I was working on the migration, Miss Kiro flagged around 4,000 Prettier errors and asked whether I wanted to do anything about them. Prettier had been added to the project without a configuration file, so it was using its opinionated defaults.

Kiro helped create a .prettierrc.json that matched the existing code style as closely as possible. This eliminated most of the reported formatting issues and established double quotes, semicolons, trailing commas, and a 100-column line width as the project's formatting baseline.

I then formatted the existing, manually maintained source files (around 98 files). The changes were limited to whitespace, quotes, and line wrapping, with no behavioural changes. Generated files and archived scripts were excluded from both Prettier and ESLint where appropriate. Deno backend functions were also excluded because they run in a separate runtime, while archived plans and documentation were excluded from Prettier. This ensured that linting no longer failed on archived Edge Function code.

For local development, I added a pre-commit hook using Husky and lint-staged. Staged files are automatically formatted with Prettier and checked and fixed by ESLint before each commit. Lovable bypasses these hooks, so I also added a GitHub Actions CI workflow that runs type checking, tests, linting, and Prettier checks, followed by a build, on every push to main and every pull request.

🌀 By now, you can probably see how this spirals out of control: I keep finding new things to work on and fix at every turn…

Broken Skitch screenshots (2008–2011)

During manual testing, I stumbled upon a post with broken images. Investigating the issue revealed that the WordPress migration had stored Evernote's homepage HTML in place of 124 defunct img.skitch.com images across 91 posts. Another three posts still hot-linked to Skitch.

For those who don't remember it, Skitch was a popular macOS screenshot-editing and annotation utility originally created by Plasq, acquired by Evernote in August 2011, and eventually discontinued. I hadn't downloaded my image library back then, so those screenshots are now lost for good.

I removed the broken image blocks and their corresponding records from all 94 affected posts, deleted the placeholder files from storage, and added a new lost-images notice to the affected posts. The notice also supports localisation (l10n).

Unit tests

While I was working on the CI pipeline, Miss Kiro pointed out that tests had become more important now that they were part of the pipeline: a failing test would cause the build to fail. Automated testing had been configured, but was barely being used. Vitest 4, jsdom, and Testing Library were wired up, and — as mentioned above — CI ran bun run test on every push to main and every pull request. Yet only two test files existed.

I addressed this in three phases to bring test coverage to an acceptable level, ultimately expanding the suite to 130 tests across 18 files.

Phase 1 focused on pure-logic unit tests for the dependency-free helpers in src/lib/. Many regression cases came from CHANGELOG.md and the fixes documented there, including an HTML-entity SSR crash, slug transliteration, overly aggressive multilingual detection, and search snippet corruption.

Phase 2 covered hooks and validation. I added unit tests for additional helpers and the image-sort transformation, along with a reusable Supabase browser-client mock and a React Query render harness. These made it possible to test mutation and query hooks deterministically, without requiring a UI.

Finally, Phase 3 added the less critical route and component smoke tests. These included match-only route assertions for current and dated legacy routes, plus a guard ensuring that unknown multi-segment URLs fall back to the not-found page. I also added a post-page rendering test covering the null, one-link, and two-link cases, including HTML-entity-decoded titles, and a clipboard-copy test for the admin image embed-code button.

All in one day

And that's how I spent the day, with a couple of meal breaks, a grocery shopping trip, and an interview thrown in. 🤞🏻

English

One morning I sat down at the computer and — to my surprise — Docker stopped, Mail said it couldn't fetch, and a dozen other errors caused by not enough disk space. I was nonplussed since I knew for sure there had to be hundreds of Gigabytes available.

I set out to investigate and met with a strange anomaly. While df reported only about 2%, du said that about 140GB is used on the 500GB SSD. How was that possible?

There was no single large file that would stand out, so there was no easy solution.

My first idea was Docker, since I didn't limit the disk usage of the containers and readsb + ADSB feeders create copious amounts of logs. But still not hundreds of Gigabytes of them, and also the logging is limited in their configuration. Docker Desktop showed normal disk usage less than 2GB.

Only when recursively trying to list the directories or files that are the culprit did I notice that a lot of directories are skipped. And then it dawned on me: I'm using iTerm2, and even with sudo, it does not have full disk access, as per MacOS policies. So one needs to explicitly go to MacOS settings and grant iTerm2 Full Disk Access in order to be able to scan everything.

After that it was easy to find that one of the directories in ~/Library/Containers/com.apple.Safari/Data/Library/WebKit/WebsiteData/Default was a whopping three-hundred-something GB. Nice for a website to use my SSD as a NAS, but how about no. The follow-up issue here is that for security reasons it isn't easy to tell which website it is, because the directory is just a hash, and the real URL is matched in Cache.db, an SQLite database. But I was curious.

You'll never guess which site it was: Microsoft Teams on the web. Apparently when you use Teams on the web, it basically downloads the whole software to cache, and then leaves it there (?). But it's never re-used, so if you join a different meeting, it does it again, and this goes on and on. I have been on a couple of interview calls lately, many in Teams, and apparently I inadvertently started to fill up my free space with copies of Teams…

After a quick purge, which can even be done in the GUI at Safari → Settings → Privacy → Manage Website Data (or you just delete the directory) I reclaimed my SSD again.

A little side-note as to why I don't use Teams Desktop. I used to, seemed like a more convenient choice, but one day I started playing World of Warcraft … only it had no sound. Whatever I did, I could't fix it, so I started investigating, and apparently the audio driver that Teams installs for whatever reason messes with WoW's sound output and without any workaround one of them had to go. (Take a wild guess.) This is how Teams Desktop and I parted ways.

Anyway, then I thought, if this happened once, it can happen again, I need to monitor this. So with heavy agentic help Miss Agent and I came up with the following bash script to be placed in ~/.zshrc which gives you a command, safari-data, that lists the top fifteen website data directories by size and matches them to their respective URL:

# --------------------------------------------------
# SAFARI DATA INSPECTOR
# --------------------------------------------------
safari-data() {
  local base_dir="$HOME/Library/Containers/com.apple.Safari/Data/Library/WebKit/WebsiteData/Default"
  local size path hash_name domain mb_size

  /usr/bin/du -sk "$base_dir"/* 2>/dev/null | /usr/bin/sort -rn | /usr/bin/head -n 15 | while read -r size path; do
    hash_name="${path##*/}"
    domain=""

    # Fast path: Search shallow SQLite databases and origin descriptors first
    domain=$(/usr/bin/find "$path" -maxdepth 3 -type f \( -name "*.sqlite" -o -name "*.db" -o -name "Origin*" \) -exec /usr/bin/strings {} + 2>/dev/null | /usr/bin/grep -E -o 'https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' | /usr/bin/sed -E 's#https?://##' | /usr/bin/head -n 1)

    # Fallback path: Full scan if shallow search misses
    if [[ -z "$domain" ]]; then
      domain=$(/usr/bin/find "$path" -type f -exec /usr/bin/strings {} + 2>/dev/null | /usr/bin/grep -E -o '([a-zA-Z0-9-]+\.)+(com|org|se|net|io|app|gov|edu|co|uk|de)' | /usr/bin/grep -v -E '(apple|webkit|sqlite|schema|system|localhost|bplist|SQLite)' | /usr/bin/head -n 1)
    fi

    mb_size=$((size / 1024))
    printf "%-8s %-35s %s\n" "${mb_size}MB" "${domain:-Unknown Domain}" "($hash_name)"
  done
}

This is the first line of an example output from today when I noticed that the Unifi Console decided it needed nearly 6GB in cache, because reasons:

❯ safari-data
5839MB   unifi.ui.com                        (zerE8ilEaEBfw61aLNeQ0pQtQn1C4PrfjyDriHGiLVc)

Did any of you run into something similar? Apparently Safari's caching has caused issues before.

Swedish

Ett lite annorlunda personligt jubileumsinlägg eftersom jag fick, i present, ett tryck av Elin Östberg.

En inramad bild som hänger på en vit vägg. Bilden föreställer en gatuscen med en korvkiosk i mitten, en person i röd klänning står framför kiosken. I bakgrunden finns byggnader med röda tak och en klarblå himmel. Till vänster om kiosken står en övergångsställe-skylt.
Korv kiosk
Hungarian

Nemrég voltam életemben először lakógyűlésen. Magyarországon nem nagyon fűlött hozzá a fogam, Svédországban viszont egy kicsit másról szól ez az egész, szóval hogy érthető legyen, mesélek kicsit a rendszerről.

A lakások itt elenyésző kivételtől eltekintve egy Bostadsrättförening (Brf) nevű jogi entitásba csoportosulnak, ami magyarul nagyjából lakásszövetkezetet jelent (housing cooperation). A magyar társasházi rendszerrel ellentétben az ingatlan tulajdonosa a szövetkezet, nem a lakó, azaz amit itt a köznyelvben lakásvásárlásnak neveznek, az valójában a szövetkezetbe való bevásárlást jelenti, ami tartalmazza a lakhatáshoz való jogot (bostadsrätt). Az épület maga, a falak, a padló, a plafon a szövetkezet tulajdonában van, ahogy maga a telek is, amin a társasház áll. Illetve az is előfordulhat, hogy az utóbbit a várostól bérli a ház.

A lakhatáshoz való jog határozatlan ideig tart, amíg a tulajdonos fizeti a Brf éves díját (årsavgift), amit általában havi bontásban számláznak. Ez a Brf működési költsége – aminek a nagy része általában kamattartozás –, és havonta esedékes, szóval nagyjából a társasházi közös költségnek felel meg.

Brf-je válogatja, de az avgift általában tartalmazza a hideg- és melegvizet, gyakran a fűtést is. Az enyémben ezen kívül benne van az internet (1Gbps, optikai), csak a villanyfogyasztást számlázzák külön.

Tekintve, hogy a lakó igazából nem tulajdonos, ha ki szeretné például adni a lakását, akkor ehhez a Brf hozzájárulása szükséges. Az ilyen bérlemények másodkézből való szerződésnek (andrahandskontrakt) minősülnek, hiszen nem közvetlenül a tulajdonostól bérli a bérlő az ingatlant. Az itt kiadott lakások legtöbbje ilyen, és mint minden nagyvárosban, Stockholmban is hatalmas probléma a lakhatás, meg a csillagászati bérleti díjak. Úgyhogy aki csak teheti, és van alaptőkéje, megpróbál inkább „saját” lakást szerezni, és inkább a bankhitelt törleszteni.

A Brf akkor működőképes, ha legalább három főből áll, és van egy vezetősége (styrelse), amit nem egy erre szakosodott cég lát el (közös képviselet), hanem a Brf tagjaiból választják meg. Gyűlést legalább évente kell tartani, ahol a költségvetésről és a vezetőség tagjairól szavaznak a tagok. Na így kerültem én az idei lakógyűlésre.

Épp kétesélyes időjárás volt kint, úgyhogy a pincében, a biciklitárolóban tartottuk. Christos, az elnök, amikor meglátott, mondta, hogy majd megpróbál amit csak lehet angolul… válaszoltam, hogy ne bohóckodjon, nyugodtan menjen svédül, majd szólok, ha nem értek valamit.

Viszonylag gyorsan lezavartuk a kötelező napirendi pontokat, nagy meglepetés nem ért senkit, mert a pénzügyi jelentést előre megkapta mindenki emailben. Aztán következett a vezetőség megválasztása, az elnökünk maradt, és egy másik vezetőségi tag is, azonban a harmadik jelezte, hogy mivel a ház átadása, azaz 2020 óta Brf vezető, most már abbahagyná. Én lenyomtam egy bemutatkozást, aztán meg is választottak, úgyhogy mostantól bentről figyelem a ház dolgait. 👀

English

I just wrapped up a passion project I built in a basically a single day: Blutag, an AI-powered alt text generator for Bluesky.

A screenshot of the Blutag website, showing the title

It's open source on Github.

Blutag focuses on accessibility while respecting privacy. Your images stay private: I don't track anything and the pictures are deleted after your browser session ends.

Some features I’m particularly proud of:

  • Drag-and-drop reorder of pictures
  • Full ATProto feature consistency, including facets (rich text annotations with byte offsets)
  • AI-powered alt text suggestions to make Bluesky posts more inclusive

It’s amazing what a day of tinkering can produce! If you care about accessibility, privacy, or just love playing with AI + social tech, give it a spin and let me know what you think.

Hungarian

A hónap elején kiadott a Svéd Központi Bank (Riksbank) egy sajtótájékoztatót arról, hogy a lakosság biztosítsa a fizetéshez szükséges különböző módokat, például a készpénzt, bankkártyát és a mobiltelefonon keresztüli fizetési szolgáltatásokhoz való hozzáférést. Az indoklás szerint a jelenlegi globális helyzet, és Svédország magas szintű digitalizációja sebezhetőségekhez vezethet a fizetési rendszerekben.

A bank azt javasolja, hogy minden háztartásban felnőttenként nagyjából 1000 korona készpénz legyen otthon. Az összeget viszonyítási alapnak kell tekinteni, és egy hétre elegendő alapvető cikkek beszerzésére szolgál. Ez lehet több vagy kevesebb, attól függően, hogy hányan élnek, vagy milyen speciális igényeik vannak a háztartásban. Amennyire lehetséges, ajánlott, hogy több különböző címletben tartsák a készpénzt. Ezen kívül a lakosságot arra is ösztönzik, hogy rendszeres időközönként használjanak készpénzt, hogy a készpénzrendszerek működőképesek maradjanak.

Viszonyítási alapnak, az öt és fél év alatt, amióta itt lakom, két alkalomra emlékszem, amikor készpénzt használtam. Az első karácsonyfánkat nem lehetett Swish-el fizetni az utcai árusnál, úgyhogy kénytelenek voltunk kivenni pénzt. A második pedig akkor volt, amikor rossz árat ütött be a piacon az eladó, és a tranzakció törlése helyett inkább odaadta a különbözetet készpénzben. Igazából abban sem vagyok biztos, milyen címletek léteznek egyáltalán…

A további ajánlások szerint a háztartásoknak legalább két, különböző hálózatból származó kártyával kell rendelkezzenek, például Visa és Mastercard kártyákkal. Valamint mobiltelefonon keresztüli hozzáféréssel a fizetési szolgáltatásokhoz, például a Swish-sel, mivel ezek más mögöttes infrastruktúrát használnak, mint a kártyás fizetések. Illetve azoknak, akik általában integrált kártyaszolgáltatásokat használnak mobiltelefonjukon, például az Apple Pay-t és a Google Pay-t, kéznél kell tartaniuk fizikai fizetési kártyáikat is arra az esetre, ha a mobiltelefon lemerülne vagy nem működne. A fizikai kártya chipje offline fizetésekre is használható hálózati zavar esetén.

Itt amúgy is van hagyománya a felkészülésnek, A védelmi minisztérium alatt egy egész ügynökség felelős a polgári védelmi, katasztrófavédelmi és közbiztonsággal kapcsolatos feladatokért. Tavaly még Myndigheten för samhällsskydd och beredskap volt a neve, idéntől Myndigheten för civilt försvar. Társadalomvédelem és készültségről polgári védelemre változott.

Tavaly minden háztartásba kiküldték a „Ha válság vagy háború jön” (Om krisen ellen kriget kommer) című kiadványt olyan információkkal például, hogy hogyan működik a légvédelmi riasztórendszer, mit érdemes a otthon tartani válságos helyzetekre, mit jelent az emelt készültségi szint, stb. Még arról is van benne szó, hogyan beszélgessünk a gyerekekkel krízishelyzetről vagy háború esetén. Az MCF oldaláról le lehet tölteni az angol verziót.

Egy élénksárga füzet
Ha válság vagy háború jön

A riasztórendszert is minden évben többször tesztelik, aminek a hivatalos neve viktigt meddelande till allmänheten (Fontos üzenet a lakosságnak). Ennek az utcai sziréna részét ”Hesa Fredrik”-nek (Rekedt Fredrik) becézik, és minden március, június, szeptember és december első olyan hétfőjén, ami nem ünnepnap, pontban délután 3 órakor próbariasztást tartanak vele. Ha valaki erről nem tud, igencsak meglepő élmény tud lenni. 😅

Ezek mind nem arról szólnak, hogy rettegésben tartsák a lakosságot, vagy tüzeljék a háborús hisztériát. Olyan hasznos információkat kommunikálnak, amikről tényleg mindenkinek érdemes tudnia, hogy minél felkészültebb legyen egy esetleges válságos időszak eljövetele esetén.

English

For a while, one of my World of Warcraft characters kept showing a pop-up saying, “You have a new Herbalism specialization to unlock” every time I logged in. The thing was, I hadn’t actually unlocked anything.

After a bit of research, I discovered there was no built-in way to clear it. I had already tried every file, macro, and reset trick that works for active professions, but the message persisted. The reason? It was coming from an unlearned profession:

  • The client internally keeps a “new specialization tab” flag for each skill line ID in memory, even if the profession is no longer learned.
  • The saved variables file Blizzard_Professions.lua only handles active professions, so flags for old or unlearned professions cannot be cleared via macros or file deletion.
  • The surprising part is that the message defaults to Herbalism because it’s the first profession loaded, even if the real culprit is another long-gone profession.

I figured there wasn’t much I could do, since this issue has apparently been around since Dragonflight (and I doubt it’s high on the dev team’s bug-fix list).

Since I had Midnight and TWW Herbalism sorted, it had to be Dragonflight. I ported back to Valdrakken, and lo and behold, I had unlearned Herbalism at some point during that expansion. After re-learning it, I was finally presented with the option to unlock Bountiful Harvests! I probably just neglected this when unlearning the profession (which is completely understandable).

A screenshot from the video game World of Warcraft, showing the
“You have a new Herbalism specialization to unlock”

Lessons learned:

  1. WoW still tracks these unlocks for unlearned professions and doesn’t provide a way to clear the flags.
  2. It’s best practice to unlock profession tabs as they become available.
  3. Even though it’s purely cosmetic, the notification can still drive a person (me) nuts.
Hungarian

A szuper új keresővel megnéztem, és utoljára öt éve írtam éves olvasmánynaplót. Ezek voltak tavaly:

2025 könyvei a pokrócon

1. Will Buxton: Grand Prix

A tavalyi évet öcsém karácsonyi ajándékával kezdtem. Buxtont F1TV kommentátori ideje alatt sokat hallgattam, és gyakran szerepelt a Drive To Surviveban is a Netflixen. Bár fenntartásokkal kezeltem a működését néha, az elvitathatatlan, hogy a sportág élő lexikonaként lehet számon tartani.

Ez a vaskos kötet a Formula 1 történetét az abszolút kezdetektől egészen napjainkig dolgozza fel. Szó van a technológiai fejlődésről, a különböző csapatok történetéről, és természetesen rengeteget ír magukról a pilótákról. Iszonyú érdekes, olvasmányos, nem zavart a magyar fordítás sem. Sokat tanultam belőle olvasás közben (aminek nagy részét azóta elfelejtettem nyilván 🤪).

Érdekesség még, hogy mivel nem a Formula 1 és FIA hivatalos kiadványáról van szó, az archív képek helyett Davi Augusto — szerintem kifejezetten jó — illusztrációival lehet találkozni.

2. Mel Robbins, Sawyer Robbins: The Let Them Theory

Nekem jórészt kimaradt a TEDx Talk, a blog, meg a podcast, de sebaj, ebből a könyvből kiválóan be lehet pótolni Mel Robbins munkásságának egy jelentős részét. A címbeli elméletének a sarokköve, hogy be kellene fejezni elfecsérelni az életet olyan dolgokra, amelyekre nincs ráhatásunk. Ami, ahogy a legtöbb ilyesmi, olvasva egyszerű, a gyakorlatban nem annyira.

Szerencsére a könyv tartalmaz egy halom való életből vett, gyakran személyes, példát, és kutatási eredményeket, melyek alátámasztják a téziseket. Nekem e-könyvem van, így a digitális kiemelőfilcet használtam gyakran azokon a részeken, amikkel a leginkább sikerült kapcsolódnom.

3. Balássy Fanni: Bocs, hogy élek

A Telex könyves rovatában találkoztam vele, és rögtön megtetszett, úgyhogy otthon jártamkor beszereztem. Megértem, hogy miért nincs digitális változata, mert Korbuly Ági rajzai legalább olyan szórakoztatóak, mint a kisprózák. A célcsoport inkább a kamaszok, de engem mindig is vonzott az ifjúsági irodalom, meg szerintem bármilyen életkorban talál benne az ember legalábbis önironikus azonosulást.

Bocs, de nem lehet ennyi az egész
Bocs, de nem lehet ennyi az egész

Négyzet alakú, úgyhogy ki fog lógni a könyvespolcon.

4. Ann-Helén Laestadius: Stolen

A Stöld angol fordítása régebb óta állt a polcomon, de tavaly eljutottam hozzá, és milyen jól tettem!

A történet főszereplője Elsa, számi1 kislány, akinek a rénszarvasa orvvadászat áldozatává esik, aminek ő az egyetlen szemtanúja. A könyv során az ő sorsának alakulását lehet végigkísérni, melyben szerepet kap a számikkal szembeni rendszerszintű hátrányos megkülönböztetés, a nők szerepe a tradicionális nomád társadalomban, a hagyományok megőrzése egy nagyon megváltozott modern világban, és persze a felnőtté válás. A szerző maga is részben számi származású egyébként, Kirunában született. A fülszöveg szerint Solnában lakik, úgyhogy szomszédok is vagyunk, 20 perc biciklivel.

Természetesen a könyv jobb, de készült belőle egy Netflix film is ugyanezzel a címmel. Az érdekessége, hogy néhány svéd párbeszéd kivételével északi számit beszélnek benne, ami a finnugor nyelvcsalád finn ágának a tagja, tehát rokonunk.

5. Suzanne Collins: Sunrise on the Reaping

Bemutatnom, gondolom, nem kell. A franchise rajongójaként kötelező darab volt, és sokkal jobban lekötött Haymitch élettörténete, mint Snow elnöké. Sok mindent nem is tudnék írni spoiler nélkül, szerintem megéri. Nagyon várom a filmet, the hype is real.

6. Raskó Gabi, Grela Alexandra: Hoztok nekünk túrórudit?

Ezt édesapámtól kaptam, akiről azt kell tudni, hogy minden élethelyzetre talál egy könyvet, és ezeket betárazva sorozat-ajándékozza a családunk tagjait.

A műnek nagy sikere lett, szóval biztos ismert, de azért leírom, hogy a könyvben külföldön élő magyar gyerekek igaz történetei szerepelnek a kint élés örömeiről és nehézségeiről. Bár már nem vagyok gyerek, mint kétrendbeli emigráns azonnal megszólítva éreztem magam. Pont szerepel Japán is, Skandináviát pedig Finnország képviseli. A sztorik után Szabó Vivien pszichológus ad tippeket gyerekeknek és szülőknek, aki történetesen pont itt lakik Svédországban.

Erről is van kiváló Telexes recenzió.

7. Gárdos Péter: Hamisítók

Gárdos Péterrel Fábián Tamás készített interjút, amit viszont csak a könyv elolvasása után nézz meg, ha eddig még nem, mert elég spoileres. Szóval innen értesültem a könyvről, aminek szerencsére volt elektronikus változata, így én is azonnal hozzá tudtam jutni.

A kiszínezett, de alapvetően önéletrajzi elemekből építkező regény a szocializmusnak abban az időszakában játszódik, amikor a fő tájékozódási médium a mozikban vetített Filmhíradó volt. Természetesen az állampárt ezt is kihasználta a propaganda terjesztésére, gyakran igen kreatívan manipulálva a leadott anyagokat.

A közmédia mai állapotában igencsak érdekes olvasmány.

8. Pottyondi Edina: Közösségi irányelvek megsértése

Pont lehetett úgy rendelni, hogy megérkezzen, mire hazamegyek az ünnepekre, és még épp volt dedikálós időpont is. A vonaton Budapest felé gyorsan be is fejeztem előtte.

Hát itt Edinából tényleg kiszabadulhatott a… nem tudom mi, de kiszabadult. A novellákban nem csak a politika, de például a női lét, a gyereknevelés, a futball, vagy egészen személyes témák is szerepelnek, mint az influenszertüntetés megszervezése. Azért a kedvencem az utolsó, Az én XX. századom lett.

Footnotes

  1. Magyarul lappokként szoktuk őket emlegetni, de ez inkább szitokszó számukra, az elnyomás jelképe. ↩

English

I was to write about the books I read in 2025, but then I wanted to know when I last posted about my year in books, and once again I ran into the issue that I miss a search option which also includes post content. Google is an option since the blog's been indexed, but I also had some rollover credits in Lovable I'd lose, so things naturally escalated.

The functionality itself took three prompts, the initial explanation of the requirements and two follow-ups where I asked to fix some design issues, correct requirements that were not matched and added the extra requirements to 1) render the result page snippets as Markdown instead of raw; 2) remove HTML and orphaned Markdown code from the snippets.

The original prompt

I plan to add a search option to the site. 



From a design perspective I imagined a circular button to the left of the theme switcher in a similar style with a loupe icon. Upon clicking on it, it opens with an animation to the left revealing an input field, which is selected, so the user can start typing at once. 

The search targets post titles, post content and tags. On typing, it offers the top 5 results in a dropdown dynamically.

If more than 5 results are present the last item in the dropdown is a ”View more” link. This link or where the user presses enter takes the user to a search results page which lists all the hits with where the word was found. As an example, look at the google search results page for the site when I search for ”marathon”.

Clicking out of the search field or dropdown collapses it back to the circular loupe icon.

Fine tuning

  • The Liquid Glass-like effect of the dropdown was originally added by the agent during one of the iterations but I liked it a lot so it remained.
  • A couple of prompts were needed to get the HTML/Markdown stripping right so that orphaned tags like <ifra were also included.
  • For a better experience Markdown links — instead of being fully removed — retained the title text as plain from the [title](url) format, even if orphaned.
  • I had the context snippet expand to word boundaries not to cut words in half.
  • The bug was fixed that separate hits were counted as duplicate results (and caused a pagination error) if a post both matched on title and content, for example.
  • Added keyboard navigation for the dropdown with Enter, Esc, ArrowUp and ArrowDown keys.

Now this was all nice but then I asked Kiro to look at the implementation from a code quality perspective and based on the steering documents she identified some bad habits that Lovable reverted back to, which needed to be corrected. Examples include:

✅ Constants Centralization: all configuration values had to be centralized in src/config/constants.ts
✅ Custom Hooks Pattern: all data fetching was extracted into custom hooks with proper JSDoc
✅ Error Handling to use the centralized error handling system
✅ Loading States: dedicated skeleton components following the loading states standard were missing (for the results page)
✅ TypeScript Strict Mode and Import Order were also checked

It's quite fun having agents race each other. 😅

Analytics

After I tested searching it occurred to me that it would be splendid to know what people actually search for.

Instead of aggregation I opted for granular logging of raw data since I don't expect huge traffic. Events could come from two sources: the dropdown and the search page. Once a QA, always a QA, I found a race condition in the first implementation with my very first manual test: typing a keyword and pressing enter before the dropdown loaded results ended up being logged twice, first with zero results then once with the correct number when the results page loaded due to the enter key navigation. Eventually, Miss Agent and I agreed that the best would be to only log dropdown clicks to actual hits and the view all navigation would be caught by the results page. (Her original suggestion was to drop the dropdown logging entirely, losing valuable data from search words which yielded results people found immediately useful.)

The rest of the time I spent on prettifying the admin dashboard showing the data and implementing date range filtering with today, 7 days, 30 days, 90 days, and all time options. Error handling, constant usage, loading states & skeletons, Zod validation, and the options pattern for mutations were handled by Kiro as previously.

English

This year too we visited the annual light art festival in Stockholm inspired by Nobel Prize awarded discoveries and achievements. During the darkest time of the year it's a real treat to walk among large scale light art installations all over the inner city. It's hard to pick favorites, but here are some examples.

One Way or Another

Honoring Ralph Bunche, an American political scientist and diplomat, winner of the Nobel Peace Prize in 1950, these signposts reflect his lecture, in which he said that “The road to peace can never be traversed by threatening to fight at every bend.”

Nobel Week Lights 2025 artwork
One Way or Another

Pro Pace

As far as my experience goes back, the City Hall (Stadshuset) is always a part of Nobel Week Lights. Artwork is projected on the whole building accompanied by music. This year the installation celebrates some selected Peace Prize laureates, plus the two cities involved, Oslo and Stockholm.

I also learned that the title references the inscription on the back of the prize medal “Pro pace et fraternitate gentium”, meaning “For peace and brotherhood among men”.

Nobel Week Lights 2025 artwork
Pro Pace

Moments of Peace

I believe another recurring location for lights is the courtyard of the Nobel Prize Museum (on the other side there's the magnificent Stockholm Cathedral). This year illuminated benches were placed to sit on and listen to music and speeches by laureates.

Nobel Week Lights 2025 artwork
Moments of Peace

21.09

September 21 is the International Day of Peace. This visual and sonic fresco-like video is projected on the ceiling within the Royal Palace. I think I liked this one best, it had everything: stunning imagery, multi language narration, breathtaking placement. Pictures don't do it justice, it had to be experienced in motion, preferably lying on the floor of the palace.

Nobel Week Lights 2025 artwork
21.09

The week of the Nobel Prize ceremonies is an especially good one to be in Stockholm.