Files
DarkRoom/docs/traceability.md
T
dtourolleandClaude Opus 5 cb1d2be240 Choose the export folder by walking the server, not by typing it
The destination for a Nextcloud export was a text field. Nobody recalls the
exact spelling of a path three levels down, and getting it wrong does not
fail — `create_dir` makes whatever was typed, so a misremembered folder
becomes a new one at the root and the exports are somewhere nobody looks.

So it is picked the way the library root is picked, using the same
`FolderBrowser` model the launch screen drives: up, into, and "use this
folder", confirming the folder currently *shown* rather than one selected in
the list. Same rule in both places, so the phrase means one thing.

The model is shared; the worker is not. `settings_ui::spawn_folder_list` is a
near-twin of the launch screen's, because that one reaches into the
`LaunchController` for its session and reports onto the launch screen's error
line, while this one is handed credentials and writes to the settings page.
Factoring them together needs a function taking both controllers or a trait
implemented twice to abstract two call sites — more machinery than the twenty
lines it saves. What matters is shared already: navigation behaves identically
because both drive the same model.

The callbacks are wired in `lib.rs` rather than in `settings_ui::wire`,
because listing a remote folder needs credentials and the settings page holds
no session on purpose — it is reachable before a library is opened and must
not depend on one existing. With no account the picker says to sign in first,
rather than showing an empty list that reads as a server with no folders.

Details that are decisions rather than accidents: the picker opens at the
library root rather than at whatever half-typed path is in the field, which
would list nothing and look broken. The listing area is a fixed 180px, since a
folder with sixty children would otherwise push the rest of the settings page
off the bottom. "Up" is disabled at the root rather than hidden, so the row
does not jump as the user navigates. A failed listing leaves the picker open
on the folder it was showing — where the user had got to is not something to
discard over a dropped request. And the chosen folder saves immediately like
every other setting on a page that has no Save button.

The poll timer lives on the controller for the reason `LaunchController` keeps
its own there: a `slint::Timer` stops when dropped, so one local to the
function that starts it would be collected before the listing arrived.

Carries in-flight work from a parallel session — a segmentation pass in
dr-gpu, a sidecar cache, and the develop panel's continuing changes.

1020 tests pass, fmt clean. One clippy warning remains and is not mine:
`sidecar_cache::dir` is unused while that work is in progress.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 07:12:01 +02:00

25 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 118
TRACES tags found 199
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:152
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:152, ui/dr-ui/src/library.rs:169, ui/dr-ui/src/library.rs:2430, ui/dr-ui/src/library.rs:2462, ui/dr-ui/src/library_ui.rs:112, ui/dr-ui/src/library_ui.rs:508, 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:179
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:852, ui/dr-ui/src/lib.rs:1236, ui/dr-ui/src/lib.rs:1341, ui/dr-ui/src/lib.rs:406, ui/dr-ui/src/lib.rs:608, ui/dr-ui/src/lib.rs:937, ui/dr-ui/src/library.rs:1329, ui/dr-ui/src/library.rs:291, ui/dr-ui/src/library.rs:349, ui/dr-ui/src/library.rs:567, ui/dr-ui/src/library_ui.rs:3136, ui/dr-ui/src/sidecar_cache.rs:1
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:1289, ui/dr-ui/src/library.rs:1366, ui/dr-ui/src/library.rs:138, ui/dr-ui/src/library.rs:196, ui/dr-ui/src/library.rs:2550, ui/dr-ui/src/library.rs:349, ui/dr-ui/src/library.rs:547, ui/dr-ui/src/library.rs:567, ui/dr-ui/src/library.rs:621, ui/dr-ui/src/library_ui.rs:1046, ui/dr-ui/src/library_ui.rs:1072, ui/dr-ui/src/library_ui.rs:1088, ui/dr-ui/src/library_ui.rs:1182, ui/dr-ui/src/library_ui.rs:1444, ui/dr-ui/src/library_ui.rs:146, ui/dr-ui/src/library_ui.rs:1709, ui/dr-ui/src/library_ui.rs:179, ui/dr-ui/src/library_ui.rs:2043, ui/dr-ui/src/library_ui.rs:2115, ui/dr-ui/src/library_ui.rs:2280, ui/dr-ui/src/library_ui.rs:307, ui/dr-ui/src/library_ui.rs:3288, ui/dr-ui/src/library_ui.rs:3316, ui/dr-ui/src/library_ui.rs:365, ui/dr-ui/src/sidecar_cache.rs:1
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:179, ui/dr-ui/src/library.rs:291
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:1290
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:135
FR-DEV-6 core/dr-pipeline/src/preset.rs:1, core/dr-types/src/settings.rs:61, ui/dr-ui/src/develop.rs:833, ui/dr-ui/src/develop.rs:843, ui/dr-ui/src/lib.rs:937, ui/dr-ui/src/library.rs:1329, ui/dr-ui/src/library.rs:291, ui/dr-ui/src/library.rs:319, ui/dr-ui/src/library_ui.rs:1540, ui/dr-ui/src/library_ui.rs:333, ui/dr-ui/src/presets.rs:1, ui/dr-ui/src/settings_ui.rs:502, 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:48
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:292, ui/dr-ui/src/settings_ui.rs:1, ui/dr-ui/src/settings_ui.rs:48, ui/dr-ui/src/settings_ui.rs:561
FR-EXP-7 ui/dr-ui/src/export.rs:1, ui/dr-ui/src/library_ui.rs:2132
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:574, ui/dr-ui/src/lib.rs:292
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.rs:1366, ui/dr-ui/src/library.rs:349, ui/dr-ui/src/library.rs:621, ui/dr-ui/src/library_ui.rs:1088, ui/dr-ui/src/library_ui.rs:2132, ui/dr-ui/src/library_ui.rs:365, ui/dr-ui/src/sidecar_cache.rs:1
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:1267, ui/dr-ui/src/lib.rs:1795, ui/dr-ui/src/library.rs:1108, ui/dr-ui/src/library.rs:1131, ui/dr-ui/src/library.rs:1289, ui/dr-ui/src/library_ui.rs:1013, ui/dr-ui/src/library_ui.rs:1245, ui/dr-ui/src/library_ui.rs:187, ui/dr-ui/src/library_ui.rs:198, ui/dr-ui/src/library_ui.rs:207, ui/dr-ui/src/library_ui.rs:266, ui/dr-ui/src/library_ui.rs:276, ui/dr-ui/src/library_ui.rs:318, ui/dr-ui/src/library_ui.rs:3305, ui/dr-ui/src/library_ui.rs:381, ui/dr-ui/src/library_ui.rs:412, ui/dr-ui/src/library_ui.rs:793, ui/dr-ui/src/library_ui.rs:894, 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:1236, ui/dr-ui/src/library.rs:291, ui/dr-ui/src/library_ui.rs:333
FR-NC-9 core/dr-catalog/src/merge.rs:1, core/dr-catalog/src/sync.rs:1, core/dr-pipeline/src/sidecar.rs:212, ui/dr-ui/src/library.rs:621, ui/dr-ui/src/library.rs:748
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:1877, ui/dr-ui/src/lib.rs:56, ui/dr-ui/ui/library.slint:546
FR-UI-2 ui/dr-ui/src/lib.rs:56
FR-UI-3 ui/dr-ui/ui/collections.slint:4
FR-UI-4 ui/dr-ui/ui/app.slint:1084
FR-UI-5 ui/dr-ui/src/collections_ui.rs:1, ui/dr-ui/src/lib.rs:1911, 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:48
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:135

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