Skip to main content

Lessons Learned

Reikon’s internal dev notes keep a running list of mistakes found and fixed during development — over 60 of them at this point. Most are too specific to one component to be useful outside this codebase. The ones below generalize — they’re the kind of bug that’s worth knowing exists as a category, whether or not you ever touch Reikon’s code specifically.

Type checkers don’t catch structural errors

tsc --noEmit passing doesn’t mean your JSX is valid. Unclosed tags and bad nesting are structural errors a type checker has no opinion about — only the actual bundler (esbuild, in our case) catches them. We run a full build before every PR for exactly this reason, not just a type check.

A multi-file contract has no compiler safety net if you skip one file

Every IPC handler here needs wiring in three separate files — the handler itself, the preload bridge, and the type definition. Skip any one of the three and the method is silently unreachable from the UI. No error, no warning — it just doesn’t work, and the only way to find out is to actually click the button. Any time a feature spans multiple files by design (not by accident), assume forgetting one of them produces silence, not an error.

Tailwind’s JIT scanner only sees literal strings

className={`xl:grid-cols-${n}`} looks like it should work — the string ends up in the DOM, the class exists. It does nothing, because Tailwind’s build-time scanner never generates CSS for a class name it can’t find as literal text in your source. The string is real at runtime; the matching CSS rule was never created. We found this because a grid silently fell back to single-column for every team count except the one number that happened to also appear as a literal string elsewhere in the file. The fix is always a lookup table of literal strings, never a runtime-interpolated class name.

Don’t trust a library’s documented behavior — verify against the version you actually installed

We had code that relied on a diff-parsing library setting a binary: true flag on binary files. It never did, in the version actually installed — confirmed by reading that version’s source, not by re-reading its docs. Docs (and changelogs) drift from behavior between versions more often than you’d expect for something this mechanical.

A blocking step inside an optional feature can take down the whole flow

An AI setup wizard had a “refresh connection” step that, if it failed, blocked the user from reaching the wizard’s final screen — even though the wizard’s entire purpose was to work without that refresh ever succeeding. Any optional/best-effort step inside a flow that has to complete regardless needs its own try/catch, not a shared one with the critical path around it.

Killing a background service on Windows sometimes means killing two processes

Reikon’s since-removed Ollama integration had to stop the local server on Windows by killing both ollama.exe and ollama app.exe — a tray-resident companion process respawns the main one if you only kill the first. The general lesson outlives the feature: a “stop the service” feature should account for the platform’s own auto-restart mechanisms, not just the obvious process name.

Pin exact versions for dependencies with a narrow compatible range

A build tool in our stack only supports a specific major-version range of another dependency. A plain npm install upgrading that dependency past the supported range broke the build with no obviously-related error message. Pinning the exact version (not just a caret range) is the fix when the actual compatible window is narrower than semver implies.

Multi-line content and shell scripts don’t mix without a temp file

Passing multi-line text through a CI step’s inline command interpolation is a reliable way to get a cryptic non-zero exit code. Writing the content to a temp file first and passing --file <path> instead of inlining it sidesteps the whole class of shell- escaping problems.

A path-resolution bug can hide as “almost everything is unused”

A dead-code detector flagged nearly every export in one part of the codebase as unused. The actual bug: import paths ending in .js that really pointed to .ts files weren’t being resolved at all — the resolver was appending candidate extensions onto the existing .js suffix instead of replacing it first, so ./foo.js never matched foo.ts. The lesson less about this specific bug and more about the shape of it: when a detector that’s supposed to be selective starts flagging almost everything, suspect the detector’s input resolution before suspecting the codebase.

A comment asking humans to “keep these in sync” is a bug waiting for a tester

One calculation — a weighted score from five inputs — existed as five independently hand-written implementations across the codebase: one in the desktop UI, one in the code that saves historical snapshots, and three more spread across a separate CLI tool. One of the five even had a comment on it: “keep in sync with the other two.” That comment is an honest admission that the design already depends on a human remembering to edit three files identically, forever, every time the formula changes. An audit checking for exactly this pattern found that it had already happened: the CLI used different weightings than the other two, excluded a filter the other two applied, and defaulted a missing value to the opposite extreme (100 instead of 0) of what the other two did. None of this threw an error or failed a test — it just meant the same repository, analyzed by two different tools, could silently report two different scores. The fix was mechanical once found — one shared function, three callers — but the finding only happened because someone asked “could this exact problem exist somewhere else?” after fixing one instance of it, instead of treating the first fix as the whole job. If a comment in your own code says “keep X in sync with Y,” that’s not documentation, it’s a description of a single point of failure with a human in the loop. Worth searching for that phrase (or the pattern, even without the comment) whenever you’re already in the area.