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:
- 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.
- 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.
- Visible boundaries help readers. Someone evaluating the
codebase or considering an extension can read just
src/qvac.tsto learn the QVAC surface, justsrc/files.tsfor input, justsrc/locallens.tsfor 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
| File | Owns |
|---|---|
domain.ts | Types and AppError. |
files.ts | File discovery and adaptation. |
rag.ts | Chunking and the grounded prompt. |
qvac.ts | QVAC SDK integration. |
store.ts | Local JSON persistence. |
locallens.ts | The product workflow (LocalLensApp). |
cli.ts | Terminal interface. |
server.ts | HTTP 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.