Devlog #2
Building my portfolio as an operating system that runs in a browser tab.
Picking up from Devlog #1, where the desktop finally had bones — windows you
could drag, focus, resize, and minimize — but nothing around them. An empty
wallpaper with floating rectangles isn’t an OS; it’s a screensaver. This
milestone is about the two pieces of chrome that make it read as a real desktop
the instant it loads: the menu bar across the top and the dock along the
bottom.
A menu bar
The menu bar is mostly static — a logo that drops open into an “About / Restart”
menu, and the usual File / Edit / View / Help labels that don’t do much yet. The
one part that has to feel alive is the clock in the top-right corner. A frozen
clock immediately breaks the illusion, so it ticks once a second
Nothing fancy — a setInterval that recomputes the formatted string every
second and clears itself on unmount. The formatting is where I tripped.
The crash that ate the whole screen
I wanted the clock in Korean — “7월 26일 (일) 오후 11:24” — so I reached for the
browser’s built-in localization instead of hand-formatting dates:
const date = d.toLocaleDateString("ko-KR", { month: "short", day: "numeric", weekday: "short" })
const time = d.toLocaleTimeString("ko_KR", { hour: "2-digit", minute: "2-digit" })
And the app went white. Not a subtly-wrong date — the entire MenuBar component
threw and took the tree down with it:
Uncaught RangeError: invalid language tag: "ko_KR"
formatNow MenuBar.tsx:74
It took me an embarrassing minute because the two lines look identical at a
glance. Look again: the date says ko-KR, the time says ko_KR. A hyphen on one
line, an underscore on the next.
The fix was one keystroke — underscore to hyphen — and the shell came back.
Lesson filed away: locale tags are hyphenated, always, and Intl validates them
strictly enough to crash. Two things to fix properly later — normalize the locale
in one constant so I can’t typo it twice, and wrap the shell in an error boundary
so a single throw dims one widget instead of nuking the screen.
The dock
The dock is the fun part. I wanted the macOS-style effect where icons swell as
the cursor passes over them and the neighbors ease up too, so it feels like the
row is being pushed under a magnifying glass rather than icons popping one at a
time.
On a trackpad-less touch screen there’s no hover state, so a finger tap would
otherwise make the whole dock lurch to wherever you touched. Gating on
pointerType === "mouse" keeps touch calm and leaves the magnification as a
mouse-only flourish, which is exactly where it belongs.
Where stands
- Menu bar with a working logo menu and a live, once-a-second clock.
- Clock is localized through
Intl— after learning the hard way that the tag
must beko-KR, notko_KR. - Dock with smooth Gaussian cursor magnification and a subtle lift, mouse-only.
- The desktop finally reads as a desktop at first glance, even before any real
apps exist.
Next up
The apps themselves — About and Projects as real windows wired into the window
manager, replacing the placeholder stand-ins the dock currently launches. And,
not incidentally, the boot sequence needs a proper redesign; the current one is
placeholder-grade and doesn’t match the polish of the shell it opens into.
Comments 1
peak project twin!! keep it going!
Sign in to join the conversation.