controller holds LibraryController and the window-sizing constants every other module reads and writes through pub(super) fields, the same shape collections_ui and develop already use. open is the launch-to-scan cycle and the worker that checks the catalog file before either touches it. offline is what of a collection is on this device and the prompt that offers to change it. window fills the grid model from the catalog and drains the thumbnail fetch, which is the piece the catalog-reads-are-proportional-to-what-changed rule (docs/catalog.md §1) bears on most directly. sync is the background passes that reach beyond the loaded window: the metadata sweep, the whole-library thumbnail pass, and the exchange with the server. ratings_keywords applies a judgement or a keyword to a selection and queues the sidecar and XMP writes behind it. timeline is the capture-time sidebar and the photographer's place together, kept in one file because a restored place ends by moving the timeline marker and a scrub is a restore of one instant, so most calls between the two would otherwise cross a module boundary. grid wires the grid's own callbacks — the keyboard cursor, cell-size zoom, the routes into and out of develop — and filter_bar wires the rating, people, date and offline-scope filters, calling back into whichever of the above owns the work a filter change triggers. Extracted by item rather than by line range, so every doc comment and TRACES/GESTURE annotation stayed attached to the code it describes; the sorted set of TRACES/GESTURE lines in the new directory is identical to the original file's. Tests moved with the code they exercise, including the handful of fixtures — settle, model_of, with_catalog, zoom_cell, pinch_step — that only one target module needed and so were not worth sharing through a test_support module the way the other splits use one. Items that crossed a new module boundary were widened from private to pub(super), narrower than the whole-file access the original gave them; a few items already pub(crate) for recovery_ui or presets stayed there rather than being narrowed, since nothing needed them tightened further. mod.rs re-exports the same surface library_ui:: callers used before, so lib.rs and every other caller needed no change.
Documentation
Two audiences, two folders. Most people want the first table and never the second.
Using DarkRoom
| The manual | Every feature, pictured from the application itself — opening a library, rating and filing, developing, local masks, repair, film, panoramas, export |
| How it is driven | Every gesture and shortcut, by screen. Generated from the code, so it cannot describe one the application does not have |
The top-level README says what DarkRoom is, how to get it on each platform, and what is still missing.
Changing DarkRoom
Everything under dev/ is for someone working on the code. Start with
CONTRIBUTING.md, which says how to land a first change
without reading the rest.
The register and the record. What must be built, how it is built, and how far along it is.
| requirements.md | What the software must do — the numbered register, the decisions (D-numbers) and the spikes (S-numbers) |
| architecture.md | How it is built — crates, the GPU pipeline, the data model, sync |
| traceability.md | Generated: which requirement is claimed by which file. Never edited by hand |
| outstanding.md | What is specified and not built, and whether that is a decision or a gap |
| technical-debt.md | Compromises taken deliberately, each with the condition that retires it |
| code-health.md | What a contribution costs, per seam, measured |
Designs, one per subsystem. Each is the specification the code was built to, kept current as the code moved.
| catalog.md | The index, the library view, incremental scan, the job queue |
| storage.md | Storage backends: the seam a folder, a sync client and a Nextcloud account share |
| faces.md | Face detection, identity, clustering and the eye-state models |
| segmentation.md | How the application finds the regions a local mask snaps to |
| mask-editing.md | Painting, erasing and combining masks |
| spot-removal.md | Clone and heal as parameters in the edit graph |
| panorama.md | Alignment, projection, the chunked composite and the border fill |
| inference.md | The neural runtime and model chosen per device, with the measurements |
| display-and-extension.md | The display contract, and why the fused pipeline is already most of a plugin format |
| view-composition.md | A controller for the display layer |
| ui-navigation.md | Finding things in the interface once there are many |
Measurements. Numbers committed so a regression is a diff rather than a recollection.
| benchmarks.md | The per-commit suite: what it covers, what it does not, how to read a failure |
| bench-baseline.json | The committed numbers the suite checks against |
| frame-budget.md | What a frame costs on each device, and the decision those figures settled |
Platforms and distribution.
| distribution.md | Which channels v1 targets and what each one constrains |
| windows.md | The Windows installer, cross-built from the Linux CI |
| android-signing.md | Which key signs the APK, and keeping it |
Archive. Kept as the record of what was asked for, not as plans.
| milestone-v0.1.md | The first milestone, delivered 2026-08-30 and superseded |
| ui-refinement.md | How the interface should look; succeeded by ui-navigation.md |
Conventions
Two files here are generated and must not be edited by hand:
gestures.md and dev/traceability.md. Both come from
cargo run -p traceability and the pre-commit hook keeps them in step with
the tree. The manual's pictures are recorded by
tools/manual and live in LFS.
A design document links to the requirements it satisfies and to the code
that satisfies them. When the code moves, the link moves with it; a document
that has stopped being true goes to dev/archive/ with a note saying what
replaced it, rather than being deleted.