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 abinary: 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 bothollama.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 plainnpm 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.