Building SkySee
Context
In the past weeks, I’ve built a web application from the ground up, scratching an itch I’ve had for a long time. Since fall 2024, I’ve been the proud owner of a Swiss paragliding license, and I’ve been making use of this privilege quite a bit (but not as much as I would have liked).
My second-favorite part of a day going flying is the time spent in between flights. Whether that’s talking to other pilots, hiking, eating lunch, or taking photos. This means I’ve ended up with a whole lot of photos of other pilots: sometimes during the final leg before landing, or during their take-off, and often enough even while in the air.
Another aspect I’ve noticed is that most pilots follow the law, and have their SHV number affixed to their paraglider. The SHV is the swiss hanggliding and paragliding association, and because everything has its order around here, it assigns (based on a contract with the civil aviation authority, BAZL) unique numbers to all swiss pilots. The numbers are sequential, and have five digits these days. If you want to avoid trouble with the authorities as a pilot, you put 40 cm tall numbers on your glider, allowing others to identify you in cases of misconduct (flying in a closed airspace, for example). In practice, they seem to be rarely used.
To me as a programmer, this number smells like a nice primary key.
My idea was: If the number is openly visible, even from a distance, then it makes a good “handle” for sharing photos and videos of each other, that many pilots already take regularly anyway. What if we had a platform where SHV-license pilots can sign up with their unique number, and then send and receive photos, videos, etc. just based on that number?
Nobody else had built this, as far as I know, so I finally did. It’s called SkySee, and it’s running at skysee.ch.
Functionality
While building it, I focused on core functionality done well. None of the typical modern features of social platforms appealed to me; I wanted to build a platform that feels safe, solid, and delightful to use. So that’s what it is now!
The core flow is simple. As a photographer, you upload a photo, video, or a YouTube link; enter the SHV numbers of anyone you see in there; and upload it. Done. This works on a desktop just as well as from the phone while being outdoors.

As a “recipient”, holding a tagged SHV number, you will get an email when someone tags you. Then you can choose to accept or reject the tag on your home feed - SHV numbers 99999 and 999998 are placeholders:

Once accepted, the photo or video is stored in your “library”, where you can find it later:

Tags can be rejected just as well, in case someone misread a number (this can happen - they are not that large), in which case the tagged pilot won’t see the item anymore. When tagging someone else, you’ll also get email notifications when they accept or reject your tag. In addition, uploads can be made public, letting you share your work with friends who are not registered on the platform.
And that’s basically it. No tracking, no comments, no messages, no likes, no profiles, no gamification, no “connections”. Just nice gestures between pilots.
LLMs
It is the age of vibe coding, and I haven’t been immune. SkySee is built using the Phoenix framework, which I hadn’t used before. Even though it’s reasonably easy and joyful to use, combining a good framework with help from an LLM has proven powerful. But: I still had to think for myself, and hope that my users will feel the human touch (even if it’s just the small imperfections here and there).
Using the combination of my own brain, and the rented brain on Google’s TPUs allowed me to add some nice, important, but not necessary features to SkySee. For example, I translated it into German (easy enough, as a native speaker) and French (rather difficult for me). It supports progressive loading when scrolling through long lists of uploads. It has additional safety and review features to protect the pilots, by letting me moderate uploads. It has real telemetry and a monitoring setup. I knew how to do many or all of these things (except the French translations), but getting assistance from an LLM made it a lot easier to go from idea to implementation; while keeping everything in the scope I wanted. For a project done in my free time, I might not have gone all the way on every one of these features otherwise.
Using Gemini CLI, it did better on many details than I could have. Whether that’s using a specific tailwind CSS class I wasn’t aware of, structuring elements in a way that looks way better than what I could have come up with, or writing unit tests in the idiomatic way I hadn’t been used to yet. It’s clear to me that for any specific detail, Gemini (or another LLM) probably can write more elegant code than I could on my first try.
Yet, in everything I (or it) did, I noticed how I was more than ever responsible for discipline: making sure that tests cover the right UI flows and everything looks (somewhat) polished, fitting together. Taking care of minor inconsistencies between views. Keeping things simple, above all. As a result, I arrived at a well-working, polished, robust web application much faster (and translated into three languages!) than I otherwise could have. But I also saw the many turns at which I had to intervene to keep it fast, polished, and robust.
There’s the saying that a good programmer must be lazy, impatient, and arrogant. Lazy, to keep the code minimal and make it work the first time; impatient, to keep it fast and not get bogged down by unnecessary features; and arrogant, to work on things that others might regard too difficult.
The three virtues are all the same in the vibe coding age, but the interpretation is different now. I must be lazy, so I actually let the LLM take over things that I might not be good at, instead of busily producing mediocre code; I must be impatient, and not let the LLM get away with circuitous answers or subpar code; and arrogant, to always know that I still could have done better than the machine.