Components¶
The building blocks of krites and how they fit together.
The deterministic engine¶
krites' value is its engine, and it lives in the pkg/ tree as a deliberate
public Go API. The engine is pure Go and cgo-free, and compiles to
GOOS=js GOARCH=wasm today. It never imports a native backend directly; those
sit behind provider interfaces.
| Package | Role |
|---|---|
analyze/quality |
Sharpness and exposure maths |
analyze/dedup |
Perceptual hashing and clustering of near-duplicate bursts |
cull |
Verdict resolution from a cull profile |
develop (straighten, crop, look, bake) |
The geometry and colour edit records, and the pure-Go export bake |
shoot |
The workspace model: manifest, sidecar, verdicts, edit records, removal patches |
xmp |
The Lightroom XMP sidecar |
export |
The render, and the only path that produces pixels |
Why the WASM constraint is load-bearing¶
Keeping the engine WASM-compilable is not about shipping krites to a browser tomorrow. It is a structural check that the judgement logic has no hidden dependencies.
The moment a verdict depends on a native library, the verdict becomes platform-dependent. The same shoot could cull differently on two machines, and the reason would be buried in a linker. Compiling to WebAssembly, where none of that is available, makes any such dependency a build failure rather than a mystery.
It is enforced as a build target in the project's own CI, so it cannot quietly rot.
Provider seams¶
Every heavy, native or networked backend is injected behind a narrow interface, faked in tests, and selected from config. Each adapter is its own package, so native code never leaks into the WASM-compilable engine.
| Interface | What it does | What ships |
|---|---|---|
raw.Decoder |
Reads a RAW frame: a cheap preview for culling and export, and a full decode | The embedded-JPEG preview, in pure Go |
face.Analyzer |
Finds faces and reports eye state, expression and pose | Local ONNX models (pkg/face/onnx) |
aesthetic.Scorer |
Rates a frame's photographic strength | Nothing yet; see Limitations |
remove.Inpainter |
Fills a masked region of a frame | LaMa or MI-GAN, local ONNX |
review.Critic |
Writes a critique of one or more frames against a rubric | Gemini or Claude, off until you choose one |
Burst clustering is not a seam: it is pure Go in analyze/dedup, part of the
engine.
Adding a backend is purely additive: a new adapter package behind the same
interface, with no call-site changes. face.detector selecting between two face
detectors, and remove.backend between two inpainters, are that seam showing in
configuration.
The seam is also what makes "off by default" a real state rather than a skipped call. With a provider disabled, the native code is never loaded. See Why the heavy providers are opt-in.
The studio¶
krites studio is the primary surface: a local,
single-user, localhost-bound web UI. It is a Svelte single-page app embedded
in the binary, talking to an HTTP and SSE API over the same shoot files the CLI
uses. The cull-review grid stays responsive on 4,000-plus frames because previews
are cached and the grid is virtualised.
The studio adds no domain logic of its own. Every endpoint maps onto an engine operation, which is what keeps the CLI and the studio genuinely interchangeable mid-shoot rather than approximately so. Its API is documented in the studio API reference.
Because the app is embedded at build time, a binary built without the generate
step serves a placeholder, which is why go install produces a broken studio.
Built on go-tool-base¶
The root command, the dependency-injection container, the Cobra command tree, the config and keychain layers, the MCP server and the self-update machinery all come from go-tool-base. krites is a downstream consumer that leans on those primitives rather than re-implementing them.
That is why krites has commands it never wrote, such as
init, config,
doctor and
update, and why they behave identically in every
phpboyscout tool. It is also why the
global flags exist on every command without
krites declaring them.
Generated files carry a DO NOT EDIT banner; new commands are scaffolded rather
than hand-written, which is why every command has the same flag conventions and
the same --shoot semantics.