> ## Documentation Index
> Fetch the complete documentation index at: https://reikon.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Lessons Learned

> Generalizable bugs found during development, written as case studies.

# 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.
