gzl.dev/blog
field notes, mostly
18 posts
August 27, 2026
7 min
Nine ways to say yes
I taught a Light Phone to speak an Oura ring's protocol over BLE. Then I spent a week failing to tap a button that doesn't exist.
Light PhoneBrightOuraBluetoothAndroid
August 21, 2026
6 min
Field notes on LightOS
What the Light Phone III's OS actually is underneath: Android with the Google removed, hardware that talks in key events, and conventions nobody voted on.
Light PhoneLightOSAndroid
August 21, 2026
1 min
A tape recorder, not a voice recorder
BrightRecorder: clips named for where and when, tapes that are just folders of WAVs, and the wheel finally doing the job it was born for.
Light PhoneBrightRecorder
August 18, 2026
2 min
Why I put an app store on a phone designed to have none
Yes, I see the irony. The argument for building it, why there's no server behind it, and the afternoon I accidentally dropped everyone's apps from the index.
Light PhoneBrightMarket
August 14, 2026
2 min
A good fork says thank you
A lot of what I ship is other people's work with my name nearer the top than it deserves. Some rules I try to fork by.
Light Phoneopen source
August 11, 2026
2 min
My bug reports file themselves
Shake the phone three times and the app files its own GitHub issue. Then an AI triages the queue every hour — under strict instructions that guessing is worse than waiting.
Light PhoneAItooling
August 8, 2026
2 min
Fifteen copies of the same file
The least interesting code I maintain is the code I'd save first in a fire. On finally building the shared library.
Light PhoneKotlin
August 8, 2026
2 min
BrightMarket is out
The app store ships: browse on a desktop, install by QR, track updates on the phone, and get your own app into the catalog.
Light PhoneBrightMarket
August 7, 2026
2 min
One column of scores
Twenty-two leagues from four keyless public APIs, in one grayscale column. How BrightSports gets scores with no account and no push.
Light PhoneBrightSports
August 6, 2026
3 min
The MetroCard inside my phone
I dissolved a transit card in acetone and moved its nervous system into the battery compartment. Tap-to-pay is back, sort of.
Light PhonehardwareNFC
August 6, 2026
2 min
Every button does what you tell it to
The follow-up to the keylayout post: BrightControl itself, why the defaults took longer than the code, and the trust problem with accessibility services.
Light PhoneBrightControldesign
August 5, 2026
1 min
Roll is out
The camera app ships: a filter wheel on real hardware, purikura with four-shot strips, date backs, and a privacy lesson from launch day.
Light PhoneRoll
August 4, 2026
2 min
The keylayout file Light left behind
The scroll wheel is just key events. Everything I learned reading Generic.kl off the phone, and the accessibility service that became BrightControl.
Light PhoneAndroid
August 2, 2026
3 min
Rewriting the camera
Roll's whole design is one gesture, stolen from a contact sheet. Plus the filters that got away from me — purikura on a monochrome phone, and datamoshing experiments that mostly produce mud.
Light Phonedesign
July 29, 2026
1 min
A QR scanner, and its desktop twin
The modern world hands you addresses as QR codes and the Light Phone couldn't read them. A scanner for the phone, a generator page for every other screen.
Light PhoneLightQR
March 22, 2026
1 min
BasilNet, a year later
What broke, what I'd trim, and how the media server quietly became infrastructure for everything else I build.
Dockerhomelabself-hosting
March 8, 2026
1 min
I built a bad DSLR on purpose
It took worse pictures than my phone. It's one of the most useful things I've ever built.
hardwarephotography
January 10, 2025
1 min
Building BasilNet: My Home Server Stack
How I built a 15-service media server with Docker Compose, Gluetun VPN, and Home Assistant integrations.
Dockerhomelabself-hosting
← all posts
August 18, 2026 · 2 min

Why I put an app store on a phone designed to have none

Light PhoneBrightMarket

I am aware of how this sounds. The Light Phone III is a phone built on the idea that you should stop looking at your phone. I built an app store for it.

Let me at least explain how I got here. The launch post covers what shipped and how to get your own app listed. This is the other half: why it exists at all, what’s holding it up, and the afternoon I broke it.

The problem was never installing apps

Sideloading works fine on this phone. LightOS is Android underneath, and an APK installs the way an APK installs anywhere. The problem is what happens three weeks later, when you have fifteen sideloaded apps and every one of them updates on its own schedule, on its own GitHub release page, and nothing on the phone knows any of that.

Obtainium solves this, and solves it well. I used it for months. But it’s built for a normal Android phone, and it only knows about the repos you paste into it yourself. There was no answer to the more basic question: how does anyone find out these apps exist at all? For a while the honest answer was “a Reddit thread and word of mouth.”

So BrightMarket does the same update tracking Obtainium does, fits this phone, and adds a catalog on top. Find an app, install it, get told when it needs updating. That’s the whole product.

The uncomfortable part

An app store on a minimalist phone is still an infinite scrolling feed. I couldn’t design my way out of that, so I stopped trying and made browsing optional instead. On first launch the app asks. With Focus mode on, it shows what you have installed and what needs updating, and nothing else. No catalog, no feed, no discovery. A surprising number of people pick that mode, which I choose to read as the userbase keeping me honest.

No server, on principle and also on budget

There is no backend. The “database” is a public GitHub repo: one YAML file per app, and a CI job that rebuilds a single JSON index every hour and serves it from GitHub Pages. Submissions arrive as pull requests against that repo, which means the whole catalog has a public history — you can read every app that was ever added, by whom, and when.

Download counts come from GitHub’s own release statistics, so the “most downloaded” sort costs nothing and tracks nobody. The app never phones home. I couldn’t run analytics if I wanted to, which is the correct amount of temptation to have.

The part where I got it wrong

One afternoon in August, the GitHub Releases API started returning empty lists. Not errors — clean HTTP 200 responses with nothing in them. My builder read “no releases” and helpfully dropped those apps from the index. Other people’s apps, gone from the catalog, because of my code and someone else’s outage.

An HTTP error had always kept the previous entry. A successful empty response didn’t, because I’d never imagined one. The rule that came out of it is now written into the repo: an app is never dropped for an answer the API failed to give.

Somewhere along the way this thing became how most Light Phone III owners install community apps. I didn’t plan for that and I try not to think about it too hard. Mostly it means the bar for “good enough” moved, and the empty-response bug is what taught me where it moved to.