The open stock list showed ten rows, None to Kodak Kodachrome 64, and the other eighteen - all seven black-and-white stocks among them - could not be reached. The data was whole; the list could not be scrolled. Reproduced on the manual rig (Xvfb, xdotool, the automation hook): - a drag on the list scrolled the develop column, never the list; - a wheel run over the list scrolled the column past it, whenever the column had scrolled under that pointer in the last 800 ms - which is how the list is reached, by wheeling the column down to it. After a pause and a pointer move the wheel did reach the list; - no key did anything. The cause is Slint's routing, not the list. Since2d878c2the list was a Flickable inside the develop column's Flickable, and Slint offers every pointer event to the outermost Flickable first (input_event_filter_before_children, i-slint-core 1.17.1 flickable.rs). The column holds a press back (DelayForwarding) and intercepts the first move past 8 px on the axis it can scroll, so the list never saw a drag. For the wheel it intercepts while its own last wheel event is under 800 ms old and within 2 px, and always for a touchpad gesture that opens with TouchPhase::Started - so on a touchpad the list could get no wheel at all. The list is now a PopupWindow under the Film row. A popup is its own item tree: while it is open, events go to it and to nothing beneath it, so the list scrolls by wheel, drag and flick however the panel is nested and whatever the column did last. The alternative, standing the column down while the pointer is over the list (the sliders' hover trick), fixes the drag but not the wheel - `interactive: false` does not gate wheel interception - so it would have left the bug for touchpad users. The reason2d878c2bounded the list still holds: it is at most 320 px and never lengthens the column, and now it covers the sliders instead of pushing them down. The column cannot be scrolled while it is open, which suits a one-click question; it closes on choosing, on Escape or Back, or on a press outside it. Keys, with the list open: Up and Down move along it from the chosen stock and scroll it into view, Enter chooses, Escape or Back closes it unchanged. A popup is its own focus tree, so the keys are taken when it opens and Slint returns focus to the develop view's scope when it closes. The Film row gains a button role, so a screen reader and the automation hook can name it.
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.