Uranometria
- 4 Devlogs
- 33 Total hours
Spent this round splitting the app into actual pages, fixing a permissions wall I didn’t know existed, and making the galaxy feel less like a static diorama.
The demo mode search input lived in a corner of the galaxy view, which turned out to be a fight I couldn’t win: clicking the canvas locks your pointer for flight controls, so trying to type in an input that shares the screen meant the cursor kept getting stolen mid-keystroke.
Instead of negotiating between the two, I split them. / is now a landing page and the galaxy moved to /g/[user]. The URL now decides whose galaxy you’re looking at, which also means every galaxy is a shareable link for free. I had planned a whole /u/username share system for later and then realized the router had already handed it to me.
Demo mode looked done until I actually clicked into someone else’s repo and got nothing. My server authenticated every tree and file request with the GitHub App’s installation token, and installation tokens can only read repos the app is installed on. Works for my account, 404 for literally anyone else’s.
The fix was boring: try the token first, and if the response isn’t a success, retry anonymously. Public repos don’t need auth anyway. I briefly felt clever for the fallback, then remembered the clever thing would have been thinking about scope the first time.
Two visual features this time. First, activity flares: each repo’s pushed_at becomes a score that decays linearly to zero over 30 days, and that score drives a slow pulse on the star’s point light. Recently pushed repos visibly breathe; abandoned ones sit cold. The idea being you can see what someone is working on lately without reading anything.
Second, search and warp. Type part of a repo name, hit Enter, and the camera lerps itself to a viewpoint just off that star. One detail that mattered: the warp calls document.exitPointerLock() first, because warping while the pointer is locked leaves you staring at a star with no cursor to click anything.
Deployment, probably. It’s currently a localhost demo, and “check out my galaxy at localhost:3000” impresses no one.
This one is three small things instead of one big thing. Controls, a code viewer, and a demo mode.
The first version had WASD and a speed of 12. That was it. Flying across the galaxy felt like wading through water, vertical movement was on Space/Shift which is apparently not what anyone expects, and the camera started and stopped instantly, which felt robotic.
New setup: base speed is 30, Shift is sprint (3.5x), E goes up and Q goes down, and scrolling the wheel multiplies your speed up or down so you can be precise inside a solar system and fast between constellations. Movement also has acceleration and deceleration now via lerping the velocity vector. This alone made it feel like an actual spaceship instead of a camera on rails.
The moons in a solar system were just decoration, so now clicking one fetches the file through the server and opens a side panel with syntax highlighting via highlight.js. The trick on the server side is sending Accept: application/vnd.github.raw to the contents API, which hands back the raw file instead of a base64 blob wrapped in JSON. Saved me a decode step and a headache.
One small thing I got wrong first: I put a key on the <Canvas> to reset the camera when switching views, then forgot that clicking things while pointer lock is active is its own mess. Tooltips and clicks work fine through drei’s event system, but I did waste ten minutes confused about why my onClick wasn’t firing before realizing I had the handler on the wrong component.
There was no way to look at anyone’s galaxy except mine, which defeats the entire point of sharing a link. Now there’s a username box in the corner; type any GitHub account and it builds their galaxy from public repos. The server just hits /users/:user/repos anonymously, since public data doesn’t need the app token. Anonymous calls are rate limited to 60/hour, so this will need caching or token routing later, but it works for now.
Private repos and the public share link, probably. That means real sessions instead of my hardcoded username, which I’ve been putting off because I know it will be annoying.
Last time ended with a working pipeline: GitHub App auth on a Rust server, my repo list coming back as JSON. This time the goal was simple to say and fun to do — turn that JSON into an actual galaxy, and make stars clickable so each repo opens up into its own little solar system.
GitHub’s repo API returns an absurd amount of data per repository. Hundreds of fields, most of which I’ll never touch. So the server got a new /api/galaxy endpoint that maps each repo down to what the galaxy actually needs: name, language, stars, fork/archived flags, last push, description. The frontend never sees the raw GitHub payload, which also keeps the design honest — tokens and GitHub-shaped data stay server-side, the browser only gets our own format.
The interesting problem was layout. Repos need positions in 3D space, they shouldn’t overlap, and related repos should cluster. I went with constellations: a small heuristic buckets each repo into a group (Web, Systems, Games, AI, Tools) based on language plus keywords in the name and description. Each constellation gets a wedge of the galaxy, and inside the wedge, repos spiral outward from the group center.
Star size is log-scaled from star count, because linear scaling either flattens the difference or explodes it. Color comes from GitHub’s own language colors — a Rust repo burns orange-brown, TypeScript blue. Forks and archived repos are hidden by default; a portfolio galaxy shouldn’t be full of other people’s work.
Tooltips came from drei’s <Html> component, which pins actual DOM elements to 3D coordinates. Hover a star, get name, language, stars, constellation. Felt like cheating, in a good way.
Clicking a star now fetches that repo’s full file tree — one git/trees/HEAD?recursive=1 call through the server proxy — and lays out the top level as a system: directories become planets on an inner orbit, files become moons on an outer one, file size sets moon size, extension sets color. Two thin ring meshes mark the orbits so the structure reads at a glance.
One trap I set for myself: switching views without resetting the camera left me stranded inside the old galaxy coordinates staring at nothing. Fixed by keying the <Canvas> on the selected repo so the camera respawns with the view. The back button is a plain HTML button floating over the canvas — sometimes the boring solution is the right one.
Finished the session with a scary-looking hydration error from Next.js. The diff showed an attribute trancy-version="7.9.1" on the <html> tag that definitely isn’t in my code — because it isn’t. A translation browser extension injects it before React loads, and React correctly reports that server HTML and client HTML differ. Nothing to fix; I just learned to read hydration diffs before panicking. Any attribute that isn’t mine means an extension did it.
Moons that actually open files — a code viewer panel with syntax highlighting. And at some point the recursive tree needs real LOD handling, because a big monorepo would currently try to render everything at once.
Uranometria is the idea of turning a GitHub account into an explorable 3D galaxy repos become star systems, folders become planets, files become moons. This session was about getting the two ends of the stack alive: a Three.js scene you can actually fly around in, and a Rust server that talks to GitHub as a proper GitHub App. As a high school senior in Korea, I had to juggle studying for exams alongside this, so I ended up making quite a few silly mistakes.
Step zero was a playground: Next.js + react-three-fiber, a sun in the middle, a few planets around it, pointer-lock mouse look, and WASD flight. The flight part is a small FlyControls component that reads keys into a ref and moves the camera along camera.getWorldDirection() inside useFrame, so W always means “wherever I’m looking.” Simple, but it immediately felt like a spaceship, which is the whole point. (To be honest, I reused a lot of code from the previous project. I also know the current design is pretty bland, but I just couldn’t be bothered to change it. I’ll fix it later.)
The scene itself took ten minutes. The ecosystem took the rest of the day.
The actual goal of the session: an axum server that authenticates as a GitHub App — sign a short-lived RS256 JWT with the app’s private key, trade it for an installation token, then call /installation/repositories. Read-only Contents permission, tokens never leaving the server, exactly like the design doc says.
/api/repos proxy returning real dataRendering the actual galaxy — mapping the repo list onto star systems, with language picking the star color and stars deciding the corona. The fun part, finally.