LocalLens

Why this structure

Eight files, on purpose.

LocalLens lives in eight TypeScript files under src/. Eight isn't a magic number — it's the smallest split that gives every layer a clear boundary without leaving any of them cramped.

This page explains why it isn't one file and why it isn't thirty.

Why not one file

A single-file version of LocalLens is tempting at this size. It's also wrong, for three reasons:

  1. QVAC integration shouldn't share a file with UI code. The model lifecycle has its own concerns — lazy loading, fallback selection, workspace cleanup. Threading those through a UI handler would either bury them or duplicate them.
  2. File adapters shouldn't share a file with model inference. The folder walk and the browser file-picker normalization are independent of the embedding step. Keeping them apart lets you change one without touching the other.
  3. Visible boundaries help readers. Someone evaluating the codebase or considering an extension can read just src/qvac.ts to learn the QVAC surface, just src/files.ts for input, just src/locallens.ts for workflow. A monolith forces them to hold the whole thing in their head.

Why not over-architected

LocalLens deliberately doesn't have:

  • a deep folder hierarchy (src/services/rag/qvac/embeddings/…);
  • abstract repository or "store" interfaces;
  • a dependency-injection framework;
  • a premature vector-database abstraction;
  • a plugin system;
  • a custom build pipeline beyond bun build.

Any of those would be reasonable in a larger app. None of them are bottlenecks at this size. They'd just be ceremony you'd have to read before getting to the real code.

The shape it has

FileOwns
domain.tsTypes and AppError.
files.tsFile discovery and adaptation.
rag.tsChunking and the grounded prompt.
qvac.tsQVAC SDK integration.
store.tsLocal JSON persistence.
locallens.tsThe product workflow (LocalLensApp).
cli.tsTerminal interface.
server.tsHTTP interface.
ui/Browser interaction.

The next page expands this with what each file should not own. Boundaries are easier to defend when you know what's on the other side of them.

When to add a file

Add a new file when a piece of code starts having two reasons to change. If you find yourself reaching into one module to tweak something unrelated to its name, that's the signal — split the unrelated thing out, and let both have one job.

On this page