Riyo Takahashi

@riyo.skyphone.social

Solo dev from Japan 🇯🇵 building on atproto. Creator of SkyPhone (12M+ installs).

SkyPhone Social 1.3.2 is out. An incoming message used to take 3.3 seconds on average to show up. It now takes 0.35. Your own messages appear the instant you hit send. It changes how a conversation feels. iOS, Android and web.

SkyPhone GPS 1.3.0 is out. Messages now arrive much faster. What used to take several seconds now lands in about 0.3 seconds. It changes how a conversation feels. Available on iOS and Android.

Rate-limited my view counter per viewer: 1000 new posts a day. Then realised the viewer comes from the connecting address when nobody is logged in. Change the address, get a fresh allowance. Unlimited became "unlimited per exit node". Added a global daily cap on first-ever rows.

I blocked cross-site writes to my counting endpoint by rejecting Sec-Fetch-Site: cross-site. Counting stopped completely in production. The endpoint is on a different domain, so my own app is cross-site to it. I had only tested by setting the header by hand, which no real browser does.

A post with an image is often taller than the screen. If "visible" means "half of the element is on screen", those posts can never qualify — it is structurally impossible. My viewport was 665px, the tallest post 1044px; 15 of 122 could never be counted. Now: half the element, or enough pixels.

Fixed a bug where post views were never counted. Then my fix reopened it. React calls a ref with null before attaching a new one whenever the callback's identity changes. My feed re-renders on every translation, so the dwell timer was thrown away each time. Zero counts at 300ms, 700ms and 900ms.

Added view counts to my posts. atproto has no field for this — Bluesky returns likes, reposts, replies, quotes and bookmarks, and nothing else. So the number can only be what my own clients see. Counting it myself meant deciding who "a viewer" is when most of them aren't logged in.

SkyPhone Social 1.2.9 is out. - Browse videos in the app: new, popular, search, by author - Suggested people to follow - Shared links now open the app - Follows your device's dark mode - Fixed: video uploads, and posts not opening from notifications Available on iOS and Android.

Want to share where you are, but not exactly where you are. SkyPhone GPS lets you share your location as an approximate area. Your friends see a point a few hundred meters off, not your real position. You choose who sees what, and you can stop anytime. Available on iOS and Android.

居場所を教えたいけど、正確な場所までは知られたくない。 SkyPhone GPS には、位置をぼかして共有する機能があります。数百メートルずらした「だいたいの場所」だけが相手に見えます。友達ごとに設定でき、いつでも止められます。 iOS / Android で配信中

Wrote a test that asserted "still sets the badge when the current value can't be read". It passed. It was also checking nothing — the function returned undefined either way. Rewrote it to assert the call actually happened; it failed immediately. A green test you didn't watch fail is not a test.

Nastiest bug today: on Android, passing 0 to the badge API doesn't zero the badge — the library calls cancelAll() and empties the whole notification shade. My poll runs every 30s, so an empty inbox would have wiped likes and replies twice a minute. Found it in the source before shipping.

SkyPhone GPS 1.2.6 is out. - New: share your location as an approximate area (shifted by a few hundred meters) - Post pins now follow your location sharing setting - Account deletion removes more of your data, plus other fixes Available on iOS and Android.

Built an unread count for the app icon, then deliberately didn't ship it. The server sends the number inside push notifications — but the code that clears it wasn't on anyone's phone yet. Ship the increment before the decrement and you hand people a number that never goes away. Still holding.

Sobering realization: my entire federation identity — every account, every post — lived on one VPS with zero backups. Now: daily snapshots, 14 generations, tested restore path, all shipped to a second server. If you self-host a PDS, check your backups today.

My current workflow: AI writes the fix, then I spawn 30+ adversarial AI agents to attack it — each finding gets a "refuter" agent trying to disprove it. 3 rounds, 65+ claims, 47 confirmed real. The scariest bugs were in the fixes themselves. Trust nothing, verify everything.

Bluesky is impressive — a decentralized SNS that's still pretty niche in Japan. People tend to see it as "an X clone," but I think its real core is the tech underneath: the AT Protocol. 🧵1/6

SkyPhone Social 1.2.7 is out. One month of progress: ・Search: 22s → 3s ・Images, quote posts, and link cards ・Per-type notification settings, and swipe right to go back on any screen ・Fixed random logouts and notifications that never arrived Next up in 1.2.8: video posting in the app.

Counted lines out of curiosity: 91k in the main repo, 51k of it TypeScript and 26k SQL migrations, 152k across all repos. Written almost entirely by AI, audited adversarially by AI, directed by one human. The bottleneck isn't writing code anymore — it's deciding what's true.

Our push notifications kept dying "randomly" for weeks. Root cause: expired sessions come back as HTTP 400 with a "token expired" body, not 401 — and our refresh logic only listened for 401. Every user's polling silently died until someone happened to post. One status code. Weeks of ghost bugs.

upside of running my own Bluesky infrastructure: I extended the analysis tool to Bluesky accounts too. unlike the X side, which pays a data provider per pull, reading Bluesky posts costs me nothing. same pipeline, zero marginal cost. small reward for owning your stack.