Telling Technology · Learning in public
All episodes
EP.35 — MARKDOWN BLEED
A Claude Code workflow, on my dashboard

My finished-looking UI was leaking raw markdown

My AIOS rewrite just grew a slick new Todo board — do-next ranking, tag bars, a detail drawer. Then I actually looked at a row and saw it: literal **asterisks** and `backticks` bleeding straight through. The todos had migrated from the old dashboard carrying their raw markdown, and every list row was rendering it as text. A UI that's meant to look done, quietly wearing its source code. This is the small, relatable "oops" — and the two-line-idea fix.

Split screen: on the left, a dark dashboard Todo row showing an ugly literal title '**Ship the proposal** by `Friday`' with the asterisks and backticks visible as raw text; on the right, the same row rendered clean — bold where the asterisks were, no backticks, a finished-looking card.
⌁ before → after · one RichText renderer · one plainTitle helper
The winning rewrite grew a feature — and an ugly bug came with it ↓
01 The ugly part

Every row was wearing its own asterisks

The todos came over from the old dashboard with their formatting intact — **bold**, `inline code`, the lot. In the old drawer that markdown got rendered. In the new Todo board's list rows, it didn't: the title was printed as a plain string, so the asterisks and backticks showed up as literal characters. Every row read like a diff of itself. Nothing was broken — it just looked unfinished, which on a UI that's supposed to look finished is its own kind of broken.

claude code — desktop
> why do my todo rows show literal **asterisks** and backticks?
The imported titles carry raw markdown, and the new list rows print the title as a plain string — no renderer. So `**Ship it**` renders as the four characters, not bold. The drawer formats markdown; the rows never did.
A dashboard Todo list where several rows show unrendered markdown in their titles — literal double-asterisks around words and backticks around inline code — making a polished-looking board read like raw text.
raw markdown, on screen
The imported titles kept their ** and ` — and the rows printed them as-is
02 Where it showed

The feature that surfaced it: a real Todo board

The bug was loud because the surface was new and dense. 3merged — the AIOS rewrite a fresh Claude model crowned the winner back in EP.30 — just earned its own dedicated Todo board (app/todos/page.tsx + TodosView.tsx, ~384 lines): do-next ranking, one-at-a-time show filters, clickable by-tag bars, and cards that open a detail drawer. A lot of rows, tightly packed — so a stray asterisk in every title wasn't a corner case, it was the whole wall. Good features are great bug-finders: they put the flaw somewhere you finally have to look at it.

app/todos/page.tsx + TodosView.tsx
> give 3merged a proper Todo board — ranking, tag filters, a detail drawer
TodosView.tsx — do-next ranking, one-at-a-time show filters, clickable by-tag bars, a card that opens a detail drawer. ~384 lines. Every one of those rows renders a title.
The new AIOS Todo board: a ranked do-next list on the left, a row of clickable by-tag bars across the top, and a detail drawer open on the right showing one todo's full formatted description.
do-next · tags · drawer
The new board — chip filters up top, a detail drawer open on the right
03 The fix

Two tools, because rows and drawers want opposite things

The tempting fix is "render markdown everywhere." Wrong: a title row wants one clean line, not a mini-document with headings and code blocks. So the fix is a pair. A RichText renderer — mirrors the old dashboard's todo-drawer formatter — renders full markdown where there's room, the detail drawer. A plainTitle helper — strips markdown down to plain text — runs on every row title, wherever titles appear: Today rows, ActionableList, the Work view's next-action, Library rows. Render in the drawer, strip in the rows. One import came in dirty; the cleanup had to reach every place a title is shown, not just the board that made it obvious.

RichText.tsx · plainTitle()
> render markdown in the drawer, but strip it from every row title
RichText → formats **bold**, `code` for the drawer. plainTitle() → returns the words only, no markers, for rows in Today · ActionableList · Work · Library. Same source string, two renderers, two jobs.
Two code snippets side by side: a small RichText React component that renders bold and inline code, and a plainTitle helper function that strips markdown markers from a string — with an arrow to a clean, asterisk-free Todo row.
render vs strip
RichText formats the drawer; plainTitle cleans every row title
🧹

The real work was finding every row, not writing the helper

Stripping markdown from a string is a few lines. The catch is that titles render in more places than the feature you're building — plainTitle had to be threaded through Today rows, ActionableList, the Work next-action, and Library rows too. Miss one and the asterisks reappear somewhere you weren't looking. A one-import bug becomes a five-surface fix.

04 The receipts

Before, after, and the two small pieces that did it

One dirty row and its clean twin, the board the bug lived on, and the two tools — RichText to render, plainTitle to strip. Tap any image to enlarge it and read exactly what's on screen.

THE BOARD, CLEAN

Same data, no asterisks — the row finally looks as done as it is

The todos didn't change; the way they're shown did. Rows render as one clean line, the drawer renders the full formatting, and the migrated markdown that was bleeding through is now handled at exactly the two places it should be. The meta-kicker writes itself: the rewrite a fresh model called the winner back in EP.30 isn't done winning — it's still absorbing lessons and shedding rough edges the judge never thought to test for. Winning once isn't the same as staying finished.

The finished AIOS Todo board with every row rendering as a clean single line — bold where it should be bold, no literal asterisks or backticks anywhere — and a detail drawer open showing fully formatted markdown.
AIOS · 3merged · TodosView.tsx + RichText + plainTitle · fixed 2026-07-11
06 Steal this

Render in the drawer, strip in the rows — the pattern, for free

If you've ever migrated content and watched raw markdown bleed into your UI, this is the whole fix in two pieces: a tiny renderer for the roomy surface, a tiny stripper for the tight one. Same source string, two jobs. Everything in this episode is free and open — clone it, run it, make it yours.

component RichText.tsx helper plainTitle() rule render vs strip, per surface catch patch every row site
the pattern # Migrated content carries raw markdown. Don't render it everywhere: # <RichText/> renders full markdown in the drawer · plainTitle(s) strips it for every row title (Today · ActionableList · Work · Library)

The extracted RichText + plainTitle snippet is being cleaned up for release. Comment RICHTEXT on the post and the bot DMs you the moment it's public.

Next episode

Next: the winner earns its migration — one SQLite spine, Core first

A polished Todo board and a markdown fix are surface wins. The real prize the fresh model pointed at back in EP.30 is underneath: stop building more Next.js apps, move everything onto one SQLite spine with clean import/export. Core first, UI second — the migration parked to August, next up.

A darkened preview of the AIOS surfaces, hinting at the Core-first SQLite migration still to come.
drops soon · follow so you don't miss it