LDBD
/
All posts

LDBD Dev Log #5 — One Last Sweep Before Telling Anyone: The Font Was Never Connected, and Dark Mode Was Asleep

Before letting anyone in, I walked through the house one more time. The font had never actually been connected, and dark mode was built but asleep. Notes from a five-stage UI overhaul, plus the nine points my first real user raised — worked through one by one.

Dev log #4 was about stretching the charts to ten years and doubting the score one more time. Around then, I was taking screenshots for the blog and caught myself hesitating.

The features were all there. There was a feed, a leaderboard, charts, and a prediction detail page. But when I asked myself whether I’d be comfortable posting these screens to a community as they were, I couldn’t say yes. I was about to start telling people about the service. The house needed cleaning before guests arrived.

I picked the screens apart with Claude to figure out why. The verdict wasn’t simply “it’s ugly.” It was that important information and secondary information sat side by side at the same size, in the same color, so when you opened a page, nothing told you where to look first. The leaderboard and the feed were both rows of small gray text and chips (those little pill-shaped labels), and card shapes, icons, colors, and font sizes drifted from page to page. I’d polished the logo and the landing page once before; that was the storefront sign. This time the problem was the whole interior. There was one piece of good news: about eighty percent of the problems weren’t page-by-page fixes; they were the kind that go away once you set shared rules for color, cards, icons, and numbers.

As it happened, my first real user review arrived around the same time. Someone on Threads had offered to try out people’s apps and review them, I left a comment, and a kind, thorough review came back — nine specific points, from someone who had used the service end to end. And the points didn’t stop at the screen. They reached the question of what this service is even asking a first-time visitor to do. So this entry is a record of two jobs run side by side: overhauling the UI in five stages, and working through those nine points.

Fixing rules, not pages

As usual, I didn’t start with code. I started with a planning document — a survey of the whole codebase that pinned every inconsistency down to file and line, combined with a manual visual pass over real screenshots. The inconsistencies it found were more concrete than I expected. The green that means “up” was scattered across five different color values. Cards were a mix of the official component and custom bordered boxes — nearly forty of the custom kind. The notification bell was an emoji, which renders differently on every device, while the icon right next to it came from an icon library, so the pair never quite matched. Page titles changed size from page to page.

The overhaul got split into five stages: foundations first (font, color, icons, dark mode), then shared UI pieces (cards, empty states, loading placeholders), then the two most-viewed screens (feed and leaderboard), then the remaining pages brought under the same rules, and finally a very thin layer of motion. The order mattered — the shared rules have to exist before pages can borrow them. Each stage lived on its own branch and only merged after an independent AI review and a check on the preview (the pre-deploy staging build). One stage had to finish before the next began.

I also decided up front what would not change: the color palette (emerald and gold), the information structure, the features, and the copy. This was maintenance, not a rebrand.

The site’s font had never been applied

The foundation stage immediately turned up a bug. Fonts on the site are assigned through CSS variables — named labels — and the label for the body font pointed at itself. Where it should have said “use this font,” it effectively said “use whatever this label says,” in a loop. The real font I had prepared was never connected to anything, and every heading and paragraph had been rendering in each device’s default font instead. That had been true since the day the service opened. The build never complained, and to the eye it just read as “the type feels a bit plain,” so nobody caught it.

While rewiring it, I brought in Pretendard, a font that draws both Korean and Latin well, self-hosted. There was a trap here too: in the theme settings of the new CSS tooling (Tailwind v4), letting one label reference another can look fine on the surface while the connection silently breaks. I flattened the references into an explicit font list. It was the same kind of mistake I’d just been burned by, and I nearly repeated it in a different spot.

Dark mode was more absurd. Looking at the preview, I noticed that when Chrome switched to dark, every other site went dark and ours stayed put. Digging into the code, the dark-mode styles were already written, everywhere. What was missing was the switch that turns them on. The whole screen was covered in rules saying “in dark mode, use this color,” and nothing anywhere declared “dark mode is on,” so all of it lay dormant. The fix was a short script that reads the device’s dark setting and declares it before the first paint. Since dark mode was effectively turning on for the first time, I also swept the whole UI for light colors hard-coded in ways that would stay white on a dark screen. Exactly one thing turned up: the knob of a toggle switch, which is supposed to be white in both themes anyway.

The font and dark mode taught the same lesson. Built and switched on are two different things. And this class of problem doesn’t get caught by the build, or by a casual glance.

One green couldn’t do two jobs

I expected the color cleanup to be a mechanical substitution. Gather the five scattered greens under a single label that means “up.” Instead of writing raw color values, you name colors by meaning — “up,” “down” — and design systems call these labels semantic tokens. Put the dark-mode variant inside the label, and the fourteen places that lost contrast in dark mode would fix themselves.

But after the substitution, the preview looked off. The newly unified green was somehow murky, and it clashed with the brand green on the buttons. Following the thread, this turned out to be a dilemma, not a mistake. Used as text on a white background, a green has to be dark enough to read. Used as the fill of a badge or a distribution bar, it has to be the same bright green as the buttons to look like it belongs. One label couldn’t satisfy both roles.

The answer was to split the roles: a darker tone for text and thin accents, the button’s brighter tone for fills. Color feels like a matter of taste, but the real order of operations is legibility (contrast) first, brand consistency within that constraint. That was the lesson of this stage.

Forty mismatched cards, and the filters too

The shared-surfaces stage started with cards. Five places used the official card component; nearly forty drew their own borders, each with slightly different corner radii and padding, and in dark mode the two kinds sat at visibly different background brightness — neighboring cards where one glowed and one sank. Everything converged on a single shared panel component. Empty-state notices and loading placeholders (the gray shimmer before content arrives) became shared components too; the empty states had been using five different spacings, and now there is one.

Somewhere in here I asked Claude a question: where do I actually see the “lift” that makes a card float above the background? The answer was that in light mode you’re not supposed to see it. In light mode the card and the background are both white, so only a thin border shows; in dark mode the card sits slightly brighter than the background and floats. It’s what choosing restraint over loud drop shadows buys you. It also taught me that this kind of fix can read as “did anything change?” until someone explains it.

The filters got unified in this stage too. The leaderboard’s filter and the feed’s tabs had different shapes, so the leaderboard’s style became the standard for filters, tabs, and form selectors across the site. Emoji in UI controls — the bell, charts, medals, padlocks — were all replaced with library icons. One trap here: I cleared every emoji out of the screen code, but emoji hiding inside translation strings, like “👤 Human,” escaped the sweep, and I later found them sitting untouched on the settings screen.

Avatars made the screens readable

The change that made the biggest difference was the avatars. Until now the leaderboard and feed were dense tables of names and numbers. Participants without a profile photo now get an avatar — a gradient from a fixed palette plus initials, both derived from their handle. It’s pure CSS, no external library, and bots get a small bot icon overlaid so they can’t be mistaken for humans. There were two traps. One is a hydration mismatch: if the server-rendered avatar and the browser-rendered avatar come out different, you get an error — so the color computation can’t use randomness or the clock, and has to give the same answer every time. The other was contrast: white initials on the lighter gradients dropped to 1.9:1 and became hard to read. Darkening the light end of the palette fixed it.

The feed card got rebuilt so the important signal reads first. The most painful finding from the survey was that the single most important signal — up or down — was the smallest element on the card, a tiny triangle. Now a thin direction-colored edge runs down the card’s left side, and the direction badge moved up next to the asset name; secondary details like timeframe and market dropped to muted text, reducing chip clutter. The “show more” button on long reasoning texts also hid a subtle bug: it used to guess truncation from a 240-character count, so on wide screens a short text would show in full while a “show more” button sat there doing nothing when clicked. It now measures whether the text actually overflowed, and only appears when something is really cut off.

On the leaderboard, the expert numbers got folded away. Statistics like 34 · [-61, +367] — sample size and confidence interval — used to be exposed to every visitor. Instead of deleting them, they collapsed into a single small dot colored by confidence tier; press it and the details open. Fold the expert information, don’t throw it away. Ranks 1–3 traded medal emoji for gold, silver, and bronze numerals, and the six-column table that forced horizontal scrolling on phones became a card list. There was an accessibility catch too: that dot — the only path to the confidence interval — was 8px, too small for a finger. The visible size stayed; the tap area grew to 24px.

And I found a hole in the flow. After polishing the prediction detail page to match the feed’s rules, I noticed the feed card’s only link to it was a small date at the bottom — and only on resolved cards. If you build a destination, build the road. The whole card is clickable now.

The final stage, motion, stayed deliberately light: cards lift slightly on hover, the like button pops, the feed fades in, the landing numbers count up. All of it turns off for anyone with “reduce motion” set on their device. One more trap lived here. The count-up renders the real value on the server first, for search engines — and if you don’t pin a locale on the number formatter, the server and the browser group digits differently (1.234 versus 1,234) and you get another hydration mismatch. Pinning the locale closed it.

How the five stages actually ran

The process might be more interesting than the result this time. A lot of the work was “one rule, dozens of files,” so for jobs like color substitution and card unification I wrote the rule once and assigned one AI agent to each file, running them in parallel. The files don’t overlap, so nothing collides.

When a stage finished, it went through three layers of review. First, an independent AI review from four angles: do the up/down colors mean what they should, are the foundations actually wired up, do contrast and accessibility hold, did any existing behavior break. But the findings didn’t get applied as-is. I pushed back on each one, filtering out matters of taste and misreadings, keeping only the real problems. Then a second AI reviewed it again, and finally I checked light, dark, and mobile myself on the preview.

The most valuable catch from all that review wasn’t design — it was a rule violation. In the landing page’s top-five table, some AI bots were showing a human icon. LDBD has a rule that a non-human participant must always present as an AI bot, and an old bug — bots with missing info falling back to a human shape — was about to get faithfully re-encoded into the new avatars. The division of labor sorted itself out, too: AI reviews caught code, rules, accessibility, and hydration; I caught what only shows up on a real screen — the green that didn’t match, the card that gave no hint where to click. Each stage closed because different eyes caught different things.

The first real user’s nine points, alongside the overhaul

Now for the nine points from the intro. This was the first time in two months that eyes other than mine and an AI’s had landed on these screens. Checked against the code, the nine sorted into three layers: four bugs and frictions with confirmable causes, two gaps in the information structure, and the rest aimed at the service’s direction. The four bugs got fixed right alongside the overhaul work.

The one that stung first was color. The feed card’s left edge carried the up/down direction color, and the reviewer asked: shouldn’t that be success/failure color instead? They were right. On resolved cards, the hit/miss chip already used green and red — so one color system was carrying two meanings on the same card. A card that bet down and won came out wearing a red edge and a green success chip at the same time, colliding head-on with the intuition that green means good. Open cards keep direction color, since there’s no result yet; resolved cards now use result color on the edge. Nothing is lost — the direction badge next to the asset name already says which way the bet went.

“Show more” came up again, too. Because the whole card is clickable, barely missing that small toggle sends you to the detail page. The reviewer’s prescription was to remove it — and there I went the other way. Removing the toggle would force every long reasoning text through the detail page, which is exactly the accidental navigation the reviewer disliked, made mandatory. Instead the tap area grew to a full-width row, and even the bottom fade — the visual hint that text is cut off — became part of the pressable area. Sometimes you accept the diagnosis and rethink the prescription.

The other two were plain bugs. The new-and-rising toggle at the bottom of the leaderboard bounced the page to the top when pressed — it was secretly a link, using the browser’s default scroll-to-top behavior on navigation, so that one link got scroll preservation. And on the blog list, posts with many tags spilled outside their card; the culprit was a single missing line-wrap rule.

Screens that had passed multiple layers of AI review still had problems that one person, actually using the service, spotted from an angle none of the reviews covered. That’s the part of this entry I keep chewing on.

A page that exists but doesn’t

The fifth point was “there’s no page for what I predicted — I can’t tell where I stand.” The interesting part is that the page exists. A profile page that gathers your scores and prediction list is right there, but to the reviewer, it didn’t feel like one. Digging in, the feeling was correct. The menu label reads “Profile,” which doesn’t suggest your prediction list lives behind it, and once inside you get an archive of past records grouped by asset — not an answer to “how many are in progress right now, and when do they resolve?” The page existed; it just wasn’t answering the user’s question.

So instead of building a new page, the top of that profile page got a status block: how many predictions are open, how many resolve today and this week, the latest results — plus an open/resolved filter on the prediction list. It all derives from data the page already loaded, so no extra server requests. Rather than the surface request — “build a per-asset page for my predictions” — it answers the question underneath: can I see where I stand?

The critique that hurt most set the direction

The last layer wasn’t a bug. It was identity: is this a service where I predict, or a service where I watch AI predictions to inform my own decisions? The reviewer’s core suggestion was to make up my mind. And it wasn’t the first time — in the competitiveness diagnosis from the last post, the AI had pointed at the same spot: the immediate value for a signed-out visitor was buried. Two diagnoses arriving independently and agreeing made the decision impossible to keep postponing.

The answer I landed on was that LDBD has two kinds of users. For the viewer, LDBD is a scoreboard of AI (and human) predictions: verified track records and per-asset tallies — eight out of twelve AIs currently calling this asset up — to weigh against your own judgment. That’s exactly the value this reviewer expected. For the contributor — anyone with prediction skill or an AI agent of their own — LDBD is a stage for public verification. The reward isn’t prize money but exposure and credibility; someone who does well here can carry that record like a business card and bring people to whatever they build next. Good contributors make the board more useful to viewers, and more viewers raise the reward for contributors. The old front-and-center slogan, “beat the AI,” isn’t being thrown away; it steps back to being a signpost that nudges a visitor into their first prediction.

One proposal didn’t make it in. Of “show me who’s accurate and what they’re buying right now,” the first half — who’s accurate, how they read the market — is accepted. But a screen that lets you follow a specific person’s open predictions is not getting built. Hiding the authors of open predictions is a deliberate design that keeps copycats from collapsing the game, and a screen that reads as “buy what this person bought” risks crossing into investment advice. One critique doesn’t overturn a principle.

For the record, I didn’t let one reviewer’s opinion justify a full pivot — only the points that matched existing diagnoses were adopted. The direction is set, but the execution remains: rewriting the landing page in this language, and deciding what “home” should be after login — the remaining information-architecture gap. Both will show up in this log.

What this round taught

Built and switched on are two different things. The font was assigned but never connected; dark mode had styles but no switch. Both pass the build and slip past the eye. Once you’ve confirmed something exists, confirm separately that it’s on.

One color can’t hold two jobs. Text legibility and brand-matched fills can’t come from the same green. And accessibility constraints like contrast outrank taste.

Consistency comes from shared components, not good intentions. Four filter shapes, two notation styles, two card lineages — none of them got fixed by promising to be consistent. They got fixed by converging on one shared component each, and the site finally wore one outfit.

Layers of AI review don’t substitute for one real user. The color edge’s double meaning passed code review and rule checks, and only someone actually using the service caught it. The reverse holds too: it took judgment to adopt what matched existing diagnoses and decline what collided with principles, rather than absorbing one review wholesale.

And “would I be comfortable posting this screen as-is” turned out to be a better litmus test than expected. Hesitating on that question is what set off the overhaul, and by the time the first guests arrived, the house was in order. After blaming “one more tweak” for the delayed launch in the last post, I realize how this sounds, but this particular round paid for itself. Screens, it seems, are never finished; they get fixed, critiqued, and fixed again. If I had to compress this entry into one sentence, it would be this: built and switched on are different things, and passing AI review is not the same as being understood by a person.

dev-logclaude-collaborationdesignuiproduct