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.

Posts in English

Filtered by primary language.

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.

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.

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.
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.

English

The title is slightly misleading, because this post is going to be about me learning more TypeScript and React. I think.

It all started with AWS organizing Dev Days in Stockholm, and since I attended the Nordic Tech Week earlier, I signed up, even though I wasn't sure it was for me… Impostor syndrome aside, of course it proved to be awesome, and I got to see two demo sessions where the presenters used Kiro, AWS's new IDE, henceforth be known as 👻, to build apps from scratch showcasing (among other things) spec-driven development and the new Kiro CLI. Kiro actually went GA only two days earlier.

I really didn't use any of the shiny new features other than the gifted 500 credits, the automatic selection of language model, and the Autopilot mode, where one mostly lets the agent run free and enjoy the results. (Writing this, I just realized that Steering would actually have been extremely useful. Added to todo list.)

Anyway, I went ahead and asked Kiro to:

"Examine this codebase written by a junior developer for software development best practices, clean code, security, possible improvements."

She started screaming that a high priority issue was the .env file being on GitHub, which is actually needed for the Lovable sync, so I eventually had to admit that there was no junior developer, after all.

👻 went:

"Ah, that changes everything! Let me reframe this for a Lovable-generated codebase where you're doing hybrid development (Lovable web editor + local IDE)."

And so a plan was formed from which I learned more than I expected. Kiro (Claude, probably, agent was set to Auto,) proposed a sequential plan to follow from quick wins to improving code quality. Here's how it went.

Caveats:

  • Code by Kiro, problems/lessons by me.
  • This is very particular for this blog and my learning path, but might be interesting to look at regardless.

Day 1: The Quick Wins (90 Minutes)

The Toast That Wouldn't Leave

The Problem: Lovable, for some unfathomable reason, decided that toasts on the admin dashboard should persist eternally (?).

// Before: 1000000ms = 1000 seconds = 16+ minutes
const TOAST_REMOVE_DELAY = 1000000;

// After: 5000ms = 5 seconds
const TOAST_REMOVE_DELAY = 5000;

Lesson: Always validate timeout values, because agents can choose weird numbers.

The N+1 Query Trap

The Problem: At some point, because "it's always been so", I modified the index page to load the latest ten blog posts. This apparently triggered 11 database queries: one for posts, then one for each post's images.

The Solution: Kiro mentioned that modern database tools support joins and recommended using them.

// Before: 11 queries
const posts = await db.posts.findMany();
const postsWithImages = await Promise.all(
  posts.map(post => db.images.findMany({ where: { postId: post.id }}))
);

// After: 1 query with join
const posts = await db.posts.findMany({
  include: { images: true }
});

Impact: Kiro was claiming significantly faster page loads, although it was quick enough already, but I suppose this might be different for larger projects with more complex queries.

Lesson: Look for N+1 patterns when fetching related data.

Error Boundaries: The Safety Net

The Problem: Any component error crashed the entire app, leaving users with a blank white screen. This wasn't really an issue, because I'd have caught it early in the Lovable preview, but again, outside of this bubble things are different.

The Solution: React Error Boundaries provide graceful degradation.

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  render() {
    if (this.state.hasError) {
      return <FriendlyErrorMessage />;
    }
    return this.props.children;
  }
}

Lesson: Error boundaries are essential for production apps: users should never see a blank screen.

Professional Logging

The Problem: 30+ console.log() statements cluttering the production console, thanks to Lovable littering the code with them when asked to investigate why something broke.

The Solution: Environment-aware logging utility. Thanks, 👻.

export const logger = {
  log: (...args) => {
    if (process.env.NODE_ENV === 'development') {
      console.log(...args);
    }
  },
  error: console.error,  // Always log errors
  warn: console.warn,
  debug: (context, ...args) => {
    if (process.env.NODE_ENV === 'development') {
      console.log(`[${context}]`, ...args);
    }
  }
};

Lesson: Debug logs are valuable during development but unprofessional in production, so they should be gated appropriately.

Day 2: Performance & UX (2 Hours)

Code Splitting with Lazy Loading

The Problem: Admin dashboard code loaded for all users, even though I'm the only person accessing it.

The Solution: React's lazy loading with Suspense.

// Before: Always loaded
import AdminDashboard from './pages/AdminDashboard';

// After: Loaded on demand
const AdminDashboard = lazy(() => import('./pages/AdminDashboard'));

<Suspense fallback={<Loader />}>
  <AdminDashboard />
</Suspense>

Impact: Smaller initial bundle, faster first page load, even though it was quick enough already.

Lesson: Lazy load features that most users don't need, helps with the bundle size.

React Query: Caching Done Right

The Problem: Lovable opted for a manual state management with useState and useEffect, no caching, and repeated fetches.

The Solution: Using React Query to handle caching, background refetching, and loading states automatically.

// Before: ~100 lines of manual state management
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
  fetchPosts().then(data => {
    setPosts(data);
    setLoading(false);
  });
}, []);

// After: ~20 lines with automatic caching
const { data: posts, isLoading } = useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts,
  staleTime: 5 * 60 * 1000,  // 5 minutes
});

Impact:

  • 80% less code
  • Automatic caching
  • Instant navigation between cached pages
  • Background refetching keeps data fresh

Lesson: Reinventing the wheel is probably pointless.

Image Optimization

The Problem: Loading full-resolution 2.5MB images when most likely the one shown was closer to a 200KB version.

The Solution: Image transformation at the CDN level.

// Add width and quality parameters
const optimizedUrl = `${imageUrl}?width=1200&quality=85`;

Impact: 92% (according to 👻) bandwidth savings, this time with faster page loads that are humanly discernible.

Lesson: Modern CDNs make optimizing images trivial with URL parameters.

Skeleton Loading States

The Problem: Lovable just added generic "Loading..." spinners.

The Solution: Content-matching skeleton screens that look way more professional, by the way.

// Instead of: <Spinner />
// Show structure that matches actual content:
<div className="space-y-4">
  <div className="h-8 bg-gray-200 animate-pulse rounded w-3/4" />
  <div className="h-4 bg-gray-200 animate-pulse rounded w-1/2" />
  <div className="h-64 bg-gray-200 animate-pulse rounded" />
</div>

Impact: Better perceived performance and avoiding layout shifts.

Lesson: Skeleton screens make loading feel faster and show users what's coming.

Centralized Constants

The Problem: Magic numbers were scattered throughout the codebase (10, 5 * 1024 * 1024, 200). Lovable tended to do this as she was focusing on the problem at hand and the individual files that were affected, not the entirety of the codebase.

The Solution: Single source of truth for configuration.

// config/constants.ts
export const POSTS_PER_PAGE = 10;
export const MAX_FILE_SIZE = 5 * 1024 * 1024;  // 5MB
export const EXCERPT_LENGTH = 200;
export const CACHE_STALE_TIME = 5 * 60 * 1000;  // 5 minutes

Lesson: Magic numbers are maintenance nightmares, so centralizing and documenting them are the way.

Day 3: Code Quality (4 Hours)

Standardized Error Handling

The Problem: Similarly to magic numbers, error handling was inconsistent: some places logged and continued, others threw, and some technical error messages leaked to visitors.

The Solution: Centralized error handling with user-friendly messages.

// Error handler with types
export class AppError extends Error {
  constructor(
    message: string,
    public type: ErrorType,
    public originalError?: unknown
  ) {
    super(message);
  }
}

export const handleError = (error: unknown, context: string): AppError => {
  logger.error(`[${context}]`, error);
  
  if (error instanceof AppError) return error;
  if (isNetworkError(error)) return new AppError('Network error', 'NETWORK');
  if (isAuthError(error)) return new AppError('Authentication failed', 'AUTH');
  
  return new AppError('Something went wrong', 'UNKNOWN');
};

export const getUserFriendlyMessage = (error: AppError): string => {
  const messages = {
    NETWORK: 'Connection problem. Please check your internet.',
    AUTH: 'Please sign in again.',
    NOT_FOUND: 'Content not found.',
    // ...
  };
  return messages[error.type] || 'Something went wrong. Please try again.';
};

Impact: Consistent error handling, better debugging, user-friendly messages.

Lesson: Standardize error handling early (and make your agent follow this pattern).

Custom Hooks for Reusability

The Problem: Data fetching logic duplicated across multiple components is a classic Lovable thing, according to yours truly. Once again, the fact that she focuses on particular problems can result in duplicated efforts.

The Solution: Extract into reusable custom hooks.

// Before: Inline in every component
const [posts, setPosts] = useState([]);
useEffect(() => {
  fetchPosts().then(setPosts);
}, []);

// After: Reusable hook
export const usePosts = (page: number) => {
  return useQuery({
    queryKey: ['posts', page],
    queryFn: () => fetchPosts(page),
    staleTime: CACHE_STALE_TIME,
  });
};

// Usage in component
const { data: posts, isLoading } = usePosts(currentPage);

Impact:

  • Removed ~150 lines of duplicate code
  • Single source of truth for data fetching
  • Easier testing
  • Consistent patterns

Lesson: Data fetching logic is best extracted into a custom hook, TIL.

TypeScript Strict Mode

The Problem: By default Lovable sets up a loose TypeScript configuration, which won't bother 99.9% of users, but this post is about code quality and best practices.

The Solution: Enable strict mode gradually. (Or just effin🐦 it and enable everything.)

{
  "compilerOptions": {
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "strictNullChecks": true
  }
}

Result: Zero errors! The previous improvements had already made the codebase type-safe.

Lesson: Strict mode catches bugs before they reach production (is the promise I wait to see.)

So how about that? I enjoyed myself immensely while doing these, and asking the agent to stop before committing or pushing anything was very useful, because one could look at the changes and learn from them. Also, agents are very good at summarizing why they do something, which also proves to be helpful in an educational setting.

🎁 Get Kiro and play with the 500 initial gift credits!

English

These past days I’ve been working on mostly under-the-hood improvements to the site in small increments.

Post URLs had a different format under WP and Jekyll following a /<year>/<month>/<day>/<slug>|.html pattern. I implemented redirects so these legacy links are forwarded to their current /post/<slug> address. While doing so Lovable recommended to further improve the function by using a calculation of Levenshtein distance between strings so that slugs would be properly matched even though the current implementation sanitizes special characters. (For example fästing becomes fsting.)

Additionally, the same URL forwarding needed to be done for pagination. /page/<number> URLs now point to /?page=<number>.

With the redirection completed I also wanted to improve SEO by providing a sitemap for search engines. It proved to be a little more complex than I expected. Lovable’s original solution was to have an edge function serve a sitemap that is later served through React Router. Other than the fact that the sitemap was being served as text/html (a React app), not application/xml or text/xml that search engines expect, the routing proved to also be problematic. Meaning that in case of a curl https://sesam.hu/sitemap.xml Google Search Console would look for /sitemap.xml as a static file which doesn't exist (404), and the React app/SitemapProxy component is never reached. So I’ve had to have the sitemap generation create an actual static public/sitemap.xml file that gets deployed with the app. Also, I added a pre-build step in package.json so that every deployment automatically generates a fresh sitemap.

To make the writing of posts easier I added a modal to the post editor backend that shows a searchable grid of uploaded images and inserts the relevant embed code in posts for me.

Finally, for sharing purposes I tried to create better OpenGraph social images for the site and posts. Lovable’s default is a screenshot of the page, which unfortunately very often captured the loading wheel only. I had to give up my original idea that if a page has images, have the first one as an OpenGraph social image, because the way the app is deployed through Lovable’s infra, which kept interfering with my SEO component. For an easy enough solution I opted for simply having a static image for every page:

sesam.hu OpenGraph image showing the site logo with pine woods as a background
English

Between 6—10 October, 2025, the Banff Mountain Film Festival visited Stockholm during its annual fall tour. It's the world’s largest film festival showcasing films in the categories of mountain sports and experiences. I went to see the Red Day of the program held at the gorgeous Bio Skandia in central Stockholm.

Fortunately, bar one, I found all of the shorts available publicly and free to rewatch on demand. You can check out the Red and Blue programs for yourself.

All of the eight short films shown were quite excellent but I wanted to share the very subjective top three list of mine.

A Team Sport

Courtney Dauwalter is definitely the GOAT when it comes to ultrarunning. This documentary follows her iconic 2023 achievement of finishing three of the most grueling 100+ mile ultra races within a year. Two of which she won. With a course record. 🤯

Of A Lifetime

I didn't know about the De Le Rue family, but looking at this documentary they are deserving renown in the skiing discipline for sure. This short is a chronicle of the journey of Xavier, Victor, and 18-year-old Mila to Antarctica where they sail through breathtaking scenery and take on equally breathtaking downhill lines. 🇦🇶

Jirishanca

I don't handle heights super well and when I tried climbing, I didn't enjoy it very much. The two protagionists of this film attempt a free ascent of the Jirishanca, an epic 6000-metre peak in the Peruvian Andes. Is it dangerous? Well, it's not tennis. 🎾

English

I can’t pinpoint when it started, but around the app’s publication, the live preview function in Lovable’s editor began to fail. It kept loading for several minutes, then reported an error. I didn’t succeed in getting the agent to find a solution. It eventually concluded it was an environment issue and advised me to contact support. The issue didn’t affect local builds, pop-out previews or the live site. What proved most annoying was that while the live preview was loading, communication with the agent was also blocked, and I couldn’t deploy updates.

In the reply from support I was told that the repository is quite large which causes the preview to time out. I had a feeling this was going to be about the images I inherited over from WordPress. As a quick fix I copied the wp-content folder to the new repository so that the linked images in old posts would not break. During development, I had these images cleaned up for duplicates — WordPress created several sizes of the same image — and had them uploaded to the bucket in their rightful place. I later deleted the folder — not realizing the images would remain in the .git folder as assets, totaling about 2 gigabytes.

My first approach was to remove the commit that contained the upload of these images, hoping that this would reduce the size of the repository. I researched interactive rebase and tried to drop the relevant commits. The hardest part was that the command used vi, so I had the age-old experience of learning to navigate it. 😅

Unfortunately this did not end up helping, the repository remained the same size and the preview was still loading endlessly. Next, I tried Git Filter-Repo on the recommendation of GPT-5. It definitely seemed intimidating, and the fact that it refused to run on the original repo wasn’t encouraging:

❯ git filter-repo --path wp-content --path public/wp-content --invert-paths
Aborting: Refusing to destructively overwrite repo history since
this does not look like a fresh clone.
  (expected freshly packed repo)
Please operate on a fresh clone instead.  If you want to proceed
anyway, use --force.

I cloned the repository and started a dry run, which succeeded. After taking a deep breath, I ran it on the original repository — and it seemed to work. One small scare followed when the command removed the 'origin' remote. I had skipped the notice about this and thought I broke something. After re-adding origin and pushing to remote, Lovable rewrote history on its side, fixing the live preview. Now, I have a conveniently small repository, as it’s supposed to be:

❯ git count-objects -vH
count: 9
size: 36.00 KiB
in-pack: 1208
packs: 1
size-pack: 891.54 KiB
prune-packable: 0
garbage: 0
size-garbage: 0 bytes

Lesson: Don’t upload gigabytes of assets to repositories.

English

Last evening was the first time everything aligned and there was solar activity as well as clear skies, so I could finally experience Aurora Borealis from Stockholm. Much less could be seen by the naked eye, but the camera captured the phenomenon much better.

Norrsken i Sundbyberg, Sverige
Norrsken i Sundbyberg, Sverige
Norrsken i Sundbyberg, Sverige