The browser already ships a table engine
The editor behind our managed-website product is built for people who have never heard of markdown. Under the hood it runs marktile's engine, which is deliberately single-layer: the text you see *is* the document, and styling is a costume worn over the source. Headings hide their `##` and stay big; bold hides its `**` and stays bold. The text never forks into a second model, so saving is trivially exact.
Then a table walks in. Tables are the one markdown construct where hiding the syntax isn't enough — strip the pipes from `| name | role |` and you're left with floating words. To *look* like a table, text needs two-dimensional layout. The received wisdom says this is where you stop pretending: build a widget, render a real table, give it its own input model, map every edit back to the source.
The received wisdom is heavy. `prosemirror-tables` is 447KB unpacked and wants the ProseMirror trinity under it — model, state, view, about 1.6MB — plus a schema'd, dual-representation document. CodeMirror 6's chassis is ~1.9MB before any table extension. Obsidian, built by people who know this domain cold, didn't ship in-table editing until 1.5. We had already half-resigned ourselves to that road.
Then, mid-conversation, a different question: what if we never leave the text? Three small facts, stacked:
**1. Source lines are already table rows.** Each line of a markdown table is its own div in the editor. Give those divs `display: table-row`, wrap cells and pipes in spans (textContent untouched — the round-trip stays byte-exact), and the browser's *anonymous table box* does the layout. It measures real glyphs, so CJK columns align to the pixel — something space-padding mathematically can't do, because a CJK glyph isn't an integer multiple of a space. We measured the gap: space-padding drifted 29–39px in our font; the table box, 0.
**2. Lock the syntax.** The hidden pipes become `contenteditable="false"`, and a `beforeinput` guard rejects any deletion whose target range would cross a pipe, a cell, or a row boundary. The structure is physically indestructible — mash Backspace at a cell edge all day — and whatever you type lands in the cell's text node, so the markdown underneath is correct by construction.
**3. Re-wrap before paint.** The engine rebuilds an edited line's DOM, wiping our spans. But MutationObserver callbacks are microtasks, and microtasks run *before paint*: re-wrap synchronously in the callback, restore the caret by character offset, and the costume change happens while the curtain is down. Zero flicker — not as a tuning achievement, but by construction.
Add Tab to hop cells and Enter to insert a row (both intercepted in capture phase, so the engine never sees them) and you have honest in-grid editing: type in a cell and the columns re-measure live, the header follows, undo works. One WebKit bug fought back — anonymous table boxes don't propagate one row's width change to its sibling rows — so on each edit we kick the block out of table context and back, also pre-paint, also invisible. Total bill: 250 lines of vanilla JS, 8 CSS rules, zero dependencies.
Prior art, honestly: every ingredient exists somewhere. HyperMD aligned source tables years ago by measuring column widths in JS — pipes visible, no grid, no locks. Everyone else took the widget road. The *combination* — source lines as table rows, locked invisible delimiters, single-layer round-trip-exact editing — we could not find anywhere public. Some closed-source editor may have invented it quietly; if you know of one, I'd genuinely love to hear about it.
The honest caveat is also the moral. This works because the engine underneath is small enough to be honest — a line is a div, the text is the textContent, there is exactly one layer. On a cathedral-sized editor framework you can't reach these seams. Sometimes the lightest solution isn't a better library. It's noticing the layout engine that has been sitting in every browser since CSS 2.1, and letting your source code wear it.
Keep reading
-
The cube solver that refuses to be confidently wrong
cubeconjure reads a Rubik's cube through your Mac's camera and tells you how to solve it. The hard part was never the solving — it was reading six colors honestly under real light. This is the story of how 'just read the stickers' turned into a lesson about not being confidently wrong, on either side of the camera.
-
Why your Mac cleaner found 47 GB (and where it really went)
A friendly teardown of how black-box Mac cleaners work — the big scary number, the invisible delete, the one thing almost none of them tell you about APFS snapshots — so you can judge any cleaner, including ours.
-
The despair that became a library
bleedblend started as a bug in reef — a sticky banner whose blur kept getting clipped at the notch. Instead of fighting Safari's chrome, we learned to paint it on purpose. This is the rabbit hole we fell down so you don't have to.