Files
DarkRoom/docs/traceability.md
T
dtourolleandClaude Opus 5 e00c99b864 Let a photograph leave: an export button, and a cache to leave from
dr-export could turn a frame into bytes and nothing could ask it to. This is
the button, and the place the bytes go.

**Everything is staged first.** An export bound for the server is written to a
local outbox and uploaded afterwards; offline is not a special case, it is the
same path with a drain that finds the server absent. Doing it the other way —
upload directly, stage only on failure — makes the failure path the one that
is rarely exercised and always broken, and a network drop mid-batch leaves
some exports existing and some not with nothing recording which. Staged first,
an export is finished the moment it is written and the upload is a promise
kept later.

The outbox sits beside the catalog rather than under the cache. dr_catalog's
cache already draws that line: passive entries are a convenience and go under
LRU, pinned ones are a promise and never do. An export awaiting upload is a
promise — the user was told it succeeded — and sweeping it for disk would
destroy the only copy. Bytes are written before the destination record, so a
kill between the two leaves an orphan the drain ignores rather than a record
pointing at nothing.

The status line says "Queued for Exports/2026", never "Exported to Nextcloud",
until it has actually landed. There is a test asserting that wording, because
the tempting shorter sentence is a claim the app cannot keep.

The drain runs on the sync pass, before the shards: a thumbnail shard can be
rebuilt from the originals and the catalog is an index, but a queued export
exists nowhere else.

`DevelopSession::render_for_export` renders the framed size rather than reusing
the frame on screen, which is deliberately viewport-sized (FR-DSP-1) — encoding
that would hand the user a soft, screen-sized file with nothing to say anything
had been lost (FR-EXP-9).

One compromise, recorded rather than hidden: the export runs synchronously on
the UI thread, so the window is unresponsive for the few hundred milliseconds
a full-resolution render and encode takes. Moving a DevelopSession and its GPU
pass to a worker is a larger change than one button earns, and it is batch
export that makes the wait intolerable rather than merely noticeable.

Still missing: the Nextcloud folder *picker*. The destination is typed into
Settings for now. `FolderBrowser` in launch.rs is already the reusable model
for it — it browses a remote tree and nothing about it is specific to choosing
a library root — but wiring it into the settings page needs a listing worker
and browser UI there, which is its own piece of work.

Carries in-flight work from a parallel session — presets, the develop copy and
paste, and the node schema's `presentation` and `enum` support. One misplaced
callback in settings_ui.rs is moved from `render` to `wire`: registered in
`render` it borrowed a `&SettingsController` into a 'static closure and would
not compile, and that file's own docs say render pushes properties while wire
connects callbacks.

992 tests pass, clippy and fmt clean. Traceability 48.3% -> 51.0%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:58:17 +02:00

24 KiB

Requirements traceability matrix

Denominators are parsed from requirements.md at run time, never hardcoded. Coverage is the intersection of tagged and defined IDs over defined IDs, so it cannot exceed 100%.

Summary

Metric Value
Source files scanned 113
TRACES tags found 186
Requirements defined 151
Requirements covered 77
Coverage 51.0% (77/151)

By type

Type Covered Defined
FR 61 97
NFR 14 48
R 2 6

Orphan tags

A tag naming an ID requirements.md does not define — what renumbering produces, and what a typo produces.

None.

Tagged requirements

ID Tagged in
FR-CAT-1 core/dr-catalog/src/scan.rs:1, core/dr-sync/src/scan.rs:93, core/dr-types/src/lib.rs:185, tools/traceability/src/lib.rs:479, tools/traceability/src/lib.rs:511, ui/dr-ui/src/activity.rs:1, ui/dr-ui/src/library.rs:1
FR-CAT-11 ui/dr-ui/src/library.rs:150
FR-CAT-12 core/dr-pipeline/src/sidecar.rs:108
FR-CAT-15 core/dr-catalog/src/schema.rs:258, core/dr-catalog/src/trash.rs:1, core/dr-sync-nextcloud/src/lib.rs:366, core/dr-sync/src/lib.rs:122, core/dr-sync/src/scan.rs:426, core/dr-sync/src/scan.rs:57, core/dr-thumbs/src/lib.rs:376, ui/dr-ui/src/collections_ui.rs:1299, ui/dr-ui/src/collections_ui.rs:817, ui/dr-ui/src/library.rs:150, ui/dr-ui/src/library.rs:167, ui/dr-ui/src/library.rs:2135, ui/dr-ui/src/library.rs:2167, ui/dr-ui/src/library_ui.rs:112, ui/dr-ui/src/library_ui.rs:476, ui/dr-ui/src/trash.rs:1, ui/dr-ui/ui/collections.slint:458
FR-CAT-1a core/dr-types/src/lib.rs:47
FR-CAT-2 core/dr-catalog/src/lib.rs:1, core/dr-catalog/src/schema.rs:1, tools/traceability/src/lib.rs:479
FR-CAT-3 core/dr-catalog/src/jobs.rs:1, core/dr-sync/src/scan.rs:69, core/dr-thumbs/src/codec.rs:1, core/dr-thumbs/src/lib.rs:1, ui/dr-ui/src/derived_sync.rs:1
FR-CAT-4 core/dr-catalog/src/lib.rs:1, core/dr-catalog/src/query.rs:1, ui/dr-ui/src/library.rs:1, ui/dr-ui/src/library_ui.rs:1
FR-CAT-5 core/dr-catalog/src/rating.rs:1, core/dr-decode/src/lib.rs:240, core/dr-decode/src/lib.rs:307, core/dr-pipeline/src/sidecar.rs:125
FR-CAT-6 core/dr-catalog/src/collections.rs:1, core/dr-catalog/src/lib.rs:1, core/dr-catalog/src/query.rs:1, core/dr-catalog/src/rating.rs:1, core/dr-types/src/selector.rs:1, ui/dr-ui/src/library.rs:177
FR-CAT-7 core/dr-catalog/src/collections.rs:1, core/dr-catalog/src/merge.rs:1, core/dr-catalog/src/sync.rs:1, core/dr-types/src/selector.rs:1, ui/dr-ui/src/collections_ui.rs:1, ui/dr-ui/src/derived_sync.rs:1, ui/dr-ui/ui/collections.slint:4
FR-CAT-8 core/dr-pipeline/src/sidecar.rs:89, ui/dr-ui/src/develop.rs:820, ui/dr-ui/src/lib.rs:1114, ui/dr-ui/src/lib.rs:1214, ui/dr-ui/src/lib.rs:405, ui/dr-ui/src/lib.rs:607, ui/dr-ui/src/lib.rs:815, ui/dr-ui/src/library.rs:1075, ui/dr-ui/src/library.rs:289, ui/dr-ui/src/library_ui.rs:2995
FR-CAT-9 core/dr-catalog/src/cache.rs:1, core/dr-catalog/src/scan.rs:1, core/dr-catalog/src/schema.rs:230, core/dr-sync-nextcloud/src/desktop_client.rs:30, core/dr-sync/src/reachability.rs:1, core/dr-types/src/lib.rs:104, ui/dr-ui/src/library.rs:1035, ui/dr-ui/src/library.rs:136, ui/dr-ui/src/library.rs:194, ui/dr-ui/src/library.rs:2255, ui/dr-ui/src/library_ui.rs:1014, ui/dr-ui/src/library_ui.rs:1056, ui/dr-ui/src/library_ui.rs:1318, ui/dr-ui/src/library_ui.rs:1583, ui/dr-ui/src/library_ui.rs:165, ui/dr-ui/src/library_ui.rs:1902, ui/dr-ui/src/library_ui.rs:1974, ui/dr-ui/src/library_ui.rs:2139, ui/dr-ui/src/library_ui.rs:291, ui/dr-ui/src/library_ui.rs:3147, ui/dr-ui/src/library_ui.rs:3175
FR-CULL-1 core/dr-decode/src/preview.rs:134
FR-CULL-2 core/dr-decode/src/locate.rs:1, core/dr-decode/src/preview.rs:161
FR-CULL-4 core/dr-catalog/src/rating.rs:1, core/dr-pipeline/src/sidecar.rs:125, ui/dr-ui/src/library.rs:177, ui/dr-ui/src/library.rs:289
FR-DEV-3 core/dr-pipeline/src/framing.rs:185
FR-DEV-3a core/dr-pipeline/build.rs:1734, core/dr-pipeline/ops/exposure.yaml:1, core/dr-pipeline/src/descriptor.rs:117, core/dr-pipeline/src/descriptor.rs:157, core/dr-pipeline/src/descriptor.rs:177, core/dr-pipeline/src/descriptor.rs:232, core/dr-pipeline/src/framing.rs:256, core/dr-pipeline/src/graph.rs:133, core/dr-pipeline/src/graph.rs:17, core/dr-pipeline/src/graph.rs:41, core/dr-pipeline/src/operation.rs:104
FR-DEV-3b core/dr-pipeline/src/descriptor.rs:177, core/dr-pipeline/src/framing.rs:256, core/dr-pipeline/src/graph.rs:41, core/dr-pipeline/src/operation.rs:104
FR-DEV-3c core/dr-pipeline/build.rs:1734, core/dr-pipeline/ops/exposure.yaml:1, core/dr-pipeline/src/graph.rs:133, ui/dr-ui/src/develop.rs:1258
FR-DEV-3d core/dr-pipeline/src/framing.rs:185
FR-DEV-3e core/dr-decode/src/lib.rs:492, core/dr-decode/src/lib.rs:612
FR-DEV-3h core/dr-decode/src/lib.rs:307, core/dr-decode/src/preview.rs:29, core/dr-pipeline/src/framing.rs:199, core/dr-types/src/lib.rs:272
FR-DEV-4 core/dr-gpu/src/lib.rs:132
FR-DEV-6 core/dr-pipeline/src/preset.rs:1, core/dr-types/src/settings.rs:61, ui/dr-ui/src/develop.rs:801, ui/dr-ui/src/develop.rs:811, ui/dr-ui/src/lib.rs:815, ui/dr-ui/src/library.rs:1075, ui/dr-ui/src/library.rs:289, ui/dr-ui/src/library.rs:317, ui/dr-ui/src/library_ui.rs:1414, ui/dr-ui/src/library_ui.rs:317, ui/dr-ui/src/presets.rs:1, ui/dr-ui/src/settings_ui.rs:434, ui/dr-ui/ui/adjust.slint:569, ui/dr-ui/ui/library.slint:455, ui/dr-ui/ui/library.slint:474, ui/dr-ui/ui/library.slint:748, ui/dr-ui/ui/settings.slint:79
FR-DSP-1 ui/dr-ui/src/lib.rs:47
FR-EXP-1 core/dr-export/src/encode.rs:1, core/dr-export/src/lib.rs:1, core/dr-types/src/settings.rs:1, ui/dr-ui/src/settings_ui.rs:1
FR-EXP-2 core/dr-types/src/settings.rs:1, ui/dr-ui/src/settings_ui.rs:1
FR-EXP-3 core/dr-export/src/lib.rs:1, core/dr-export/src/size.rs:1, core/dr-export/src/size.rs:25, core/dr-types/src/settings.rs:1, ui/dr-ui/src/settings_ui.rs:1
FR-EXP-4 core/dr-export/src/lib.rs:1, core/dr-export/src/sharpen.rs:1, core/dr-export/src/size.rs:1, ui/dr-ui/src/settings_ui.rs:1
FR-EXP-5 ui/dr-ui/src/settings_store.rs:1
FR-EXP-6 core/dr-export/src/lib.rs:1, core/dr-export/src/name.rs:1, core/dr-types/src/settings.rs:1, ui/dr-ui/src/export.rs:1, ui/dr-ui/src/lib.rs:291, ui/dr-ui/src/settings_ui.rs:1
FR-EXP-7 ui/dr-ui/src/export.rs:1, ui/dr-ui/src/library_ui.rs:1991
FR-EXP-8 core/dr-export/src/encode.rs:1, ui/dr-ui/src/settings_ui.rs:1
FR-EXP-9 core/dr-decode/src/lib.rs:409, core/dr-export/src/lib.rs:1, core/dr-export/src/lib.rs:91, core/dr-gpu/src/adjust.rs:332, ui/dr-ui/src/develop.rs:542, ui/dr-ui/src/lib.rs:291
FR-NC-1 core/dr-sync-nextcloud/src/auth.rs:132, core/dr-sync-nextcloud/src/auth.rs:44, core/dr-sync-nextcloud/src/session.rs:128, ui/dr-ui/src/launch.rs:256, ui/dr-ui/src/launch.rs:49, ui/dr-ui/src/launch_ui.rs:344
FR-NC-10 ui/dr-ui/src/export.rs:1, ui/dr-ui/src/library_ui.rs:1991
FR-NC-12 core/dr-sync-nextcloud/src/lib.rs:34, core/dr-sync-nextcloud/src/lib.rs:811, core/dr-sync/src/lib.rs:155, core/dr-sync/src/lib.rs:38, core/dr-sync/src/reachability.rs:1
FR-NC-2 core/dr-sync-nextcloud/src/session.rs:128, core/dr-sync-nextcloud/src/session.rs:34
FR-NC-3 core/dr-decode/src/locate.rs:1, core/dr-decode/src/preview.rs:161, core/dr-sync/src/capability.rs:41, core/dr-thumbs/src/lib.rs:1, ui/dr-ui/src/library.rs:1, ui/dr-ui/src/library_ui.rs:1
FR-NC-4 core/dr-sync-nextcloud/src/propfind.rs:100, core/dr-sync-nextcloud/src/propfind.rs:51, core/dr-sync/src/capability.rs:6, core/dr-sync/src/lib.rs:155, core/dr-sync/src/scan.rs:93, ui/dr-ui/src/launch.rs:49
FR-NC-5 core/dr-sync-nextcloud/src/propfind.rs:51
FR-NC-6 ui/dr-ui/src/activity.rs:1
FR-NC-6a core/dr-catalog/src/cache.rs:1, core/dr-catalog/src/schema.rs:230, core/dr-catalog/src/schema.rs:631, core/dr-types/src/selector.rs:1, core/dr-types/src/settings.rs:1, ui/dr-ui/src/lib.rs:1140, ui/dr-ui/src/lib.rs:1668, ui/dr-ui/src/library.rs:1035, ui/dr-ui/src/library.rs:854, ui/dr-ui/src/library.rs:877, ui/dr-ui/src/library_ui.rs:1119, ui/dr-ui/src/library_ui.rs:173, ui/dr-ui/src/library_ui.rs:184, ui/dr-ui/src/library_ui.rs:193, ui/dr-ui/src/library_ui.rs:250, ui/dr-ui/src/library_ui.rs:260, ui/dr-ui/src/library_ui.rs:302, ui/dr-ui/src/library_ui.rs:3164, ui/dr-ui/src/library_ui.rs:349, ui/dr-ui/src/library_ui.rs:380, ui/dr-ui/src/library_ui.rs:761, ui/dr-ui/src/library_ui.rs:862, ui/dr-ui/src/library_ui.rs:981, ui/dr-ui/src/settings_store.rs:1, ui/dr-ui/src/settings_ui.rs:1, ui/dr-ui/ui/library.slint:504
FR-NC-6c core/dr-sync-nextcloud/src/desktop_client.rs:30, core/dr-types/src/lib.rs:104, core/dr-types/src/lib.rs:186, ui/dr-ui/src/activity.rs:1
FR-NC-7 core/dr-sync-nextcloud/src/lib.rs:95, ui/dr-ui/src/derived_sync.rs:1
FR-NC-8 core/dr-pipeline/src/sidecar.rs:108, core/dr-pipeline/src/sidecar.rs:89, ui/dr-ui/src/lib.rs:1114, ui/dr-ui/src/library.rs:289, ui/dr-ui/src/library_ui.rs:317
FR-NC-9 core/dr-catalog/src/merge.rs:1, core/dr-catalog/src/sync.rs:1, core/dr-pipeline/src/sidecar.rs:212
FR-PLAT-AND-1 core/dr-types/src/lib.rs:47
FR-PLAT-AND-3 core/dr-catalog/src/jobs.rs:1
FR-PLAT-LIN-1 core/dr-types/src/settings.rs:1, ui/dr-ui/src/settings_store.rs:1
FR-RAW-1 core/dr-decode/src/lib.rs:198, core/dr-types/src/lib.rs:114, core/dr-types/src/lib.rs:185
FR-RAW-3 core/dr-decode/src/lib.rs:409, core/dr-decode/src/lib.rs:94
FR-RAW-4 core/dr-decode/src/error.rs:1
FR-RAW-5 core/dr-decode/src/lib.rs:122
FR-UI-1 ui/dr-ui/src/lib.rs:1750, ui/dr-ui/src/lib.rs:55, ui/dr-ui/ui/library.slint:546
FR-UI-2 ui/dr-ui/src/lib.rs:55
FR-UI-3 ui/dr-ui/ui/collections.slint:4
FR-UI-4 ui/dr-ui/ui/app.slint:1064
FR-UI-5 ui/dr-ui/src/collections_ui.rs:1, ui/dr-ui/src/lib.rs:1784, ui/dr-ui/ui/collections.slint:4
FR-UI-7 core/dr-pipeline/src/descriptor.rs:100, core/dr-pipeline/src/framing.rs:256
NFR-ARCH-2 core/dr-catalog/src/jobs.rs:1
NFR-ARCH-4 core/dr-catalog/src/error.rs:1, core/dr-export/src/error.rs:1, core/dr-thumbs/src/error.rs:1
NFR-OPS-1 tools/traceability/src/lib.rs:266
NFR-P1 core/dr-catalog/src/lib.rs:1, core/dr-catalog/src/scan.rs:1, tools/traceability/src/lib.rs:479
NFR-P13 core/dr-decode/src/preview.rs:134
NFR-P9 ui/dr-ui/src/collections_ui.rs:1, ui/dr-ui/src/library.rs:1, ui/dr-ui/src/library_ui.rs:1, ui/dr-ui/src/trash.rs:1
NFR-R1 core/dr-catalog/src/sync.rs:1
NFR-R2 core/dr-catalog/src/trash.rs:1
NFR-R5 core/dr-catalog/src/collections.rs:1, core/dr-catalog/src/error.rs:1, core/dr-catalog/src/schema.rs:1
NFR-R7 core/dr-gpu/src/error.rs:1
NFR-R8 core/dr-gpu/src/error.rs:1
NFR-RES-1 ui/dr-ui/src/lib.rs:47
NFR-RES-4 core/dr-catalog/src/cache.rs:1, core/dr-catalog/src/schema.rs:230, core/dr-thumbs/src/codec.rs:1, core/dr-thumbs/src/lib.rs:1, core/dr-thumbs/src/lib.rs:376
NFR-SEC-1 core/dr-decode/src/error.rs:1
R1 tools/traceability/src/lib.rs:495, tools/traceability/src/lib.rs:499
R4 core/dr-gpu/src/lib.rs:132

Not yet tagged

74 of 151 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built.

Show untagged requirements
  • FR-CAT-10
  • FR-CAT-13
  • FR-CAT-14
  • FR-CULL-10
  • FR-CULL-11
  • FR-CULL-12
  • FR-CULL-3
  • FR-CULL-5
  • FR-CULL-6
  • FR-CULL-7
  • FR-CULL-8
  • FR-CULL-9
  • FR-DEV-1
  • FR-DEV-2
  • FR-DEV-3f
  • FR-DEV-3g
  • FR-DEV-5
  • FR-DEV-7
  • FR-DEV-8
  • FR-DSP-2
  • FR-DSP-3
  • FR-DSP-4
  • FR-DSP-5
  • FR-DSP-6
  • FR-DSP-7
  • FR-DSP-8
  • FR-NC-11
  • FR-NC-6b
  • FR-PLAT-AND-2
  • FR-PLAT-AND-4
  • FR-PLAT-AND-5
  • FR-PLAT-AND-6
  • FR-PLAT-LIN-2
  • FR-PLAT-LIN-3
  • FR-RAW-2
  • FR-UI-6
  • NFR-A11Y-1
  • NFR-A11Y-2
  • NFR-A11Y-3
  • NFR-ARCH-1
  • NFR-ARCH-3
  • NFR-COMPAT-1
  • NFR-COMPAT-2
  • NFR-OPS-2
  • NFR-OPS-3
  • NFR-OPS-4
  • NFR-P10
  • NFR-P11
  • NFR-P12
  • NFR-P14
  • NFR-P15
  • NFR-P2
  • NFR-P3
  • NFR-P4
  • NFR-P5
  • NFR-P6
  • NFR-P7
  • NFR-P8
  • NFR-PORT-1
  • NFR-PORT-2
  • NFR-PORT-3
  • NFR-R3
  • NFR-R4
  • NFR-R6
  • NFR-RES-2
  • NFR-RES-3
  • NFR-SEC-2
  • NFR-SEC-3
  • NFR-SEC-4
  • NFR-SEC-5
  • R2
  • R3
  • R5
  • R6