DEV Community

shaojie gong
shaojie gong

Posted on

Getting a flashcard out was easy. Getting it into Anki had five traps.

Once I could finally read the flashcards, exporting them to Anki looked like the victory lap. It was not. Every layer had a trap, and each one only showed up after I'd cleared the one before it.

I want to write them down in order, because the shape of this — fix a thing, immediately hit the next thing — is most of what building on other people's formats actually feels like.

Trap one: the file wouldn't build. I'd hand-written the Anki schema — an .apkg is really a zip around a SQLite database — and generating it needs sql.js, which runs on WebAssembly. My export button just said "Failed to build .apkg." No detail, because I'd wrapped it in a catch that swallowed the real error. First fix was to myself: print the actual exception. The moment I did, it was obvious — a Chrome MV3 extension blocks WebAssembly by default. You have to declare 'wasm-unsafe-eval' in the manifest's content security policy or the runtime refuses to instantiate the module. One line in the manifest, and the file built.

Trap two: it built, it imported, and the math was garbage. NotebookLM writes formulas with dollar-sign delimiters, $like this$. Anki's built-in MathJax deliberately doesn't recognize dollar signs — it'd collide with people writing about actual money — so it wants ( ... ) for inline and [ ... ] for display. My cards imported with the LaTeX showing as raw source. One conversion pass on export fixed it, applied to all three export paths so I couldn't forget one later.

Trap three: the order was scrambled. Cards came into my panel reversed, and then landed in Anki in that same wrong order. The cause was one word: I was using unshift to add each card to the front of the list, which quietly reverses a batch. And the .apkg assigns each card a sort value by its position in the array — so the reversed array meant reversed cards in Anki too. Changed unshift to push. Both problems, one line.

Trap four: I decided the panel should preview the formulas too, so I pulled in KaTeX, lazy-loaded so it only downloads for cards that actually contain math. Wired it up, and half my cards went blank. The bug was mine and it was a good one: I was setting the rendered HTML by hand on a ref, then calling setState, and React's re-render wiped out the HTML I'd just injected because as far as React knew that node should be empty. The fix was to stop fighting React — put the HTML in state and let React own it through dangerouslySetInnerHTML.

Trap five was the gentlest. A user — the person actually testing this, which at this stage is basically one very patient human — imported the same set twice and got a doubled library. Fair. I added dedup on import, keyed on front-plus-back so that reimporting is a no-op but opening a different set still adds new cards. Then a Clear all button, because "reset and reimport" turned out to be the thing you want most while testing.

None of these were hard, individually. That's the part worth saying out loud. Each one was five to thirty minutes once I could see it. The cost wasn't difficulty, it was serialization — I couldn't see trap two until I'd cleared trap one, couldn't see three until two was done. You can't parallelize a tunnel. You just keep walking and hitting your head, and the only real skill is not talking yourself out of checking the next thing.

The best decision in the whole run was the smallest: unswallowing that first error. I'd hidden the real exception behind a friendly message, congratulating myself on graceful degradation, and it cost me the fastest possible path to every bug after it. "Failed to build" tells you nothing. The stack trace told me everything in one line.

What's the smallest change that's saved you the most debugging time? Mine is now permanently "log the actual error, then decide what to show the user."

— building NotebookBloom in public, #7

Top comments (0)