Add secure credential storage, sessions, and a launch screen
Login now persists properly rather than through the JSON file the test
harness was using.
dr-plat SecretStore trait plus a Secret Service backend.
Verified against the live GNOME Keyring: store,
retrieve, delete, confirm-gone all round-trip.
Session/SessionStore splits credentials from settings — the app
password goes to the keyring (FR-NC-2), while
server, login, chosen root and format selection are
ordinary config. A test asserts the credential never
appears in the config file.
LaunchModel the launch-screen state machine, testable without a
display server: sign in, approve in browser, choose
folder, tick formats, sign out.
launch.slint the screen itself, in its own file.
Absence of a secrets daemon is an explicit degraded mode, not a silent
fallback to plaintext — the screen says sign-in will not persist rather
than letting the user find out next launch. Android's Keystore backend
fails loudly for the same reason: a no-op store would look like it
worked and then lose the credential.
Two bugs caught by tests rather than by running it:
- fail() after busy() signed the user out, because busy() had already
discarded the session. A failed *scan* would have logged you out.
Busy now carries the session.
- normalise_server upgrades http:// to https:// rather than accepting
it. NFR-SEC-3 requires TLS, and silently sending a credential in the
clear is not a decision to make on the user's behalf.
launch.slint is not yet wired into app.slint. Calling slint_build::compile
twice replaces the generated module rather than adding to it, which broke
the other in-flight work on dr-ui; I reverted that immediately. Wiring it
needs an import inside app.slint, which is that work's file to change.
419 tests passing across ten crates.
This commit is contained in:
@@ -643,6 +643,16 @@ dir_validator(root)
|
||||
`folders.etag` must exist from schema v1. Adding it later means a migration plus a full re-scan of
|
||||
every user's library (ARCH §6.6).
|
||||
|
||||
**Validated against a real library, 2026-08-09.** A cold recursive scan of a 17,185-RAW library
|
||||
(7,836 CR2 + 9,349 DNG) across 334 directories completed in **34.1 s** — `Depth: 1` per directory,
|
||||
never `Depth: infinity`. Two implementation details worth keeping:
|
||||
|
||||
- **The format filter sees through VFS placeholder suffixes**, so a dehydrated `IMG.CR2.nextcloud`
|
||||
matches as the CR2 it stands for rather than being skipped as an unknown type (§9.0).
|
||||
- **Pruning is capability-gated, not assumed.** With `LocalEtags` a directory probe costs a request
|
||||
and proves nothing about children, so it is pure overhead; a test asserts zero probes in that
|
||||
case. Only `PropagatingEtags` makes an unchanged parent prove an unchanged subtree.
|
||||
|
||||
### 8.5 Sidecar conflict resolution
|
||||
|
||||
`put` with `Precondition::IfMatch(validator)`. On precondition failure:
|
||||
@@ -920,6 +930,22 @@ from schema v1.
|
||||
primary path; server previews are opportunistic. `preview_max_filesize_image` defaults to 50 MB,
|
||||
excluding many RAWs even where a provider exists.
|
||||
|
||||
**Measured against a real instance, 2026-08-09, and the case is stronger than assumed.**
|
||||
`/core/preview` returned HTTP 400 for *every* parameter combination attempted — including bare
|
||||
`?fileId=N`, and including a JPEG the same server reported as `nc:has-preview=true`. The cause is
|
||||
unexplained: it is a server-side preview configuration issue, not a request-shape error on our side.
|
||||
|
||||
Recorded as **unexplained rather than understood**, because the distinction matters if someone later
|
||||
tries to depend on this endpoint. Nothing does today — the connector treats a preview failure as a
|
||||
miss and falls through to range extraction, which is the designed behaviour rather than a
|
||||
workaround.
|
||||
|
||||
**Range extraction validated on the same library.** 262 KB read from a 21.5 MB DNG in 119 ms —
|
||||
**1.22% of the file** — yielded camera model and ISO. Extrapolated across the 17,185-file test
|
||||
library, cataloguing by whole-file fetch would move roughly 370 GB; the range path moves a few MB.
|
||||
That ratio is the difference between a viable mobile experience and an unusable one, and it is why
|
||||
§6.7 treats range reads as the mechanism and server previews as a bonus.
|
||||
|
||||
### 6.8 Sync is selective, not mirror-style
|
||||
|
||||
### 6.9 Android forbids the filesystem-scan model
|
||||
|
||||
+74
-3
@@ -458,7 +458,12 @@ Not "on first connect" as a bulk operation. Thumbnails are generated:
|
||||
|
||||
For a local library this converges on "everything, eventually", because scrolling reaches everything
|
||||
and the background pass has nothing else to do. For a remote library it converges on "what you
|
||||
actually browsed", which is the difference between a few hundred megabytes and a hundred gigabytes.
|
||||
actually browsed".
|
||||
|
||||
**Measured on a real 17,185-RAW library, 2026-08-09:** cataloguing it by whole-file fetch would move
|
||||
roughly **370 GB**; the range-extract path moves a few MB for the images actually viewed. This is
|
||||
the single largest cost difference in the design, and it is why §7.1 is a list of narrow triggers
|
||||
rather than "generate them all on connect".
|
||||
|
||||
### 7.2 How, by availability
|
||||
|
||||
@@ -466,7 +471,7 @@ actually browsed", which is the difference between a few hundred megabytes and a
|
||||
|---|---|---|
|
||||
| `Original`, local | Embedded JPEG via `dr-decode` preview path | ~200 KB read, no demosaic |
|
||||
| `Original`, no embedded preview | Full decode, downscale | Expensive — `Background` only |
|
||||
| Remote | Range-extract embedded JPEG (FR-NC-3) | 1–3 MB vs 25–100 MB |
|
||||
| Remote | Range-extract embedded JPEG (FR-NC-3) | 1–3 MB vs 25–100 MB — **measured: 262 KB of a 21.5 MB DNG, 119 ms, 1.22% of the file** |
|
||||
| Placeholder / `Offline` | None — render the offline affordance | 0 |
|
||||
|
||||
The remote path deliberately does **not** ask the Nextcloud client to hydrate the file. ARCH §9.0
|
||||
@@ -490,7 +495,73 @@ which never evict at all (FR-NC-6b).
|
||||
|
||||
---
|
||||
|
||||
## 8. What this document does not settle
|
||||
## 8. Syncing the catalog file
|
||||
|
||||
Decided 2026-08-09. **This qualifies [architecture.md §6.12](architecture.md)** — the catalog
|
||||
remains a rebuildable index, but the file itself now travels to Nextcloud. The qualification is
|
||||
worth stating precisely, because the sidecar-authoritative model is load-bearing and this is the
|
||||
one place it bends.
|
||||
|
||||
### 8.1 Why collections forced this
|
||||
|
||||
Every other thing the catalog holds has authoritative backing outside it. Ratings, labels,
|
||||
keywords, and edit graphs live in sidecars next to the images, so a rebuild recovers them.
|
||||
**Collections do not.** A manual collection is a set of images the user assembled by hand; nothing
|
||||
in the filesystem records it. Losing the catalog loses them, and no rescan brings them back.
|
||||
|
||||
So collections need to be durable across devices somehow. Syncing the catalog file is the chosen
|
||||
mechanism.
|
||||
|
||||
### 8.2 What the file sync does and does not carry
|
||||
|
||||
Only **collections and their membership** merge. The rest of a catalog describes *local* state —
|
||||
folder mtimes, cache file paths, job rows, `tier_actual` — and importing another device's version
|
||||
of those would be actively wrong. The downloaded remote is read for its collections and discarded.
|
||||
|
||||
This is what keeps §6.12 substantially intact: nothing here makes the local database authoritative
|
||||
for anything a rebuild could not recover. The catalog is still deletable. What syncs is one table
|
||||
pair that had no other home.
|
||||
|
||||
### 8.3 Two hazards the implementation must handle
|
||||
|
||||
**A WAL database is not one file.** Committed transactions can sit in `catalog.sqlite-wal` with the
|
||||
main file lagging, so copying `catalog.sqlite` alone uploads a torn snapshot — internally consistent
|
||||
as of some older point, silently missing everything since. Upload therefore runs a `TRUNCATE`
|
||||
checkpoint and then SQLite's backup API, which serialises against concurrent writers rather than
|
||||
racing them. It never copies the live file.
|
||||
|
||||
**Integer primary keys are not identities.** Two devices each allocate `collections.id = 1` for
|
||||
different collections, so a row-level merge keyed on the integer id would collide them. Collections
|
||||
therefore carry a **UUID**, and membership maps across devices by **image content hash**. The
|
||||
integer ids stay local and are never compared across catalogs.
|
||||
|
||||
### 8.4 Merge rules
|
||||
|
||||
| Concern | Rule | Why |
|
||||
|---|---|---|
|
||||
| Which collection wins | Higher `revision` — a counter bumped per local edit. `modified` only breaks an exact tie | A device with a skewed clock cannot silently overwrite real work. The same reason FR-NC-9 avoids mtime for sidecars |
|
||||
| Membership | **Set union**, not last-writer-wins | Two devices adding different images to one collection keep both. The exception — a removal racing an addition — resolves toward the addition, which is recoverable by removing it again. A lost addition is not |
|
||||
| Deletion | Tombstone (`deleted = 1`) carrying a revision | Without it, merging against a device that still holds the collection resurrects it. With a revision, deletion competes on equal footing with a rename |
|
||||
| An image the remote has and we do not | Skip the membership row | It joins on a later merge, once a scan has catalogued the file. Not an error |
|
||||
| A remote from a newer schema | Decline before attaching | Attempting it would fail mid-transaction rather than declining cleanly |
|
||||
|
||||
Merging is idempotent: running it twice reports no changes the second time. That property is tested,
|
||||
because a merge that oscillates would upload on every sync forever.
|
||||
|
||||
### 8.5 What was rejected
|
||||
|
||||
**Replace-if-newer.** The literal reading of "sync the file and take the newer one". Rejected
|
||||
because it is not a merge: whichever device syncs second loses every collection the first did not
|
||||
have. Binary SQLite files do not merge, so "newer wins" means "older is destroyed".
|
||||
|
||||
**A `collections.drsc` sidecar at the library root.** The alternative that would have kept §6.12
|
||||
untouched, merging as text the way edit sidecars do. Viable, and cheaper in machinery, but it means
|
||||
a second serialisation format and a second merge implementation for the same data. Recorded here
|
||||
because if the SQLite path proves troublesome, this is the fallback with a known shape.
|
||||
|
||||
---
|
||||
|
||||
## 9. What this document does not settle
|
||||
|
||||
- **FTS.** `Selector::Text` is a `LIKE` scan over filename and keywords. Adequate at 50k; if free
|
||||
text over description and title becomes a real workflow, an FTS5 table is the answer, and it is
|
||||
|
||||
+34
-34
@@ -9,18 +9,18 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n
|
||||
|
||||
| Metric | Value |
|
||||
|---|---|
|
||||
| Source files scanned | 44 |
|
||||
| TRACES tags found | 37 |
|
||||
| Source files scanned | 63 |
|
||||
| TRACES tags found | 49 |
|
||||
| Requirements defined | 143 |
|
||||
| Requirements covered | 36 |
|
||||
| **Coverage** | **25.2%** (36/143) |
|
||||
| Requirements covered | 49 |
|
||||
| **Coverage** | **34.3%** (49/143) |
|
||||
|
||||
### By type
|
||||
|
||||
| Type | Covered | Defined |
|
||||
|---|---|---|
|
||||
| FR | 27 | 90 |
|
||||
| NFR | 7 | 47 |
|
||||
| FR | 36 | 90 |
|
||||
| NFR | 11 | 47 |
|
||||
| R | 2 | 6 |
|
||||
|
||||
## Orphan tags
|
||||
@@ -33,36 +33,49 @@ _None._
|
||||
|
||||
| ID | Tagged in |
|
||||
|---|---|
|
||||
| FR-CAT-1 | [`tools/traceability/src/lib.rs:473`](../tools/traceability/src/lib.rs#L473), [`tools/traceability/src/lib.rs:505`](../tools/traceability/src/lib.rs#L505) |
|
||||
| FR-CAT-1a | [`core/dr-types/src/lib.rs:23`](../core/dr-types/src/lib.rs#L23) |
|
||||
| FR-CAT-2 | [`tools/traceability/src/lib.rs:473`](../tools/traceability/src/lib.rs#L473) |
|
||||
| FR-CAT-1 | [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-sync/src/scan.rs:50`](../core/dr-sync/src/scan.rs#L50), [`core/dr-types/src/lib.rs:174`](../core/dr-types/src/lib.rs#L174), [`tools/traceability/src/lib.rs:473`](../tools/traceability/src/lib.rs#L473), [`tools/traceability/src/lib.rs:505`](../tools/traceability/src/lib.rs#L505) |
|
||||
| FR-CAT-1a | [`core/dr-types/src/lib.rs:36`](../core/dr-types/src/lib.rs#L36) |
|
||||
| FR-CAT-2 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1), [`tools/traceability/src/lib.rs:473`](../tools/traceability/src/lib.rs#L473) |
|
||||
| FR-CAT-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1) |
|
||||
| FR-CAT-4 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1) |
|
||||
| FR-CAT-5 | [`core/dr-decode/src/lib.rs:216`](../core/dr-decode/src/lib.rs#L216) |
|
||||
| FR-CAT-9 | [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-types/src/lib.rs:80`](../core/dr-types/src/lib.rs#L80) |
|
||||
| FR-CAT-6 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/query.rs:1`](../core/dr-catalog/src/query.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1) |
|
||||
| FR-CAT-7 | [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1), [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1) |
|
||||
| FR-CAT-9 | [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-types/src/lib.rs:93`](../core/dr-types/src/lib.rs#L93) |
|
||||
| FR-CULL-1 | [`core/dr-decode/src/preview.rs:96`](../core/dr-decode/src/preview.rs#L96) |
|
||||
| FR-CULL-2 | [`core/dr-decode/src/preview.rs:123`](../core/dr-decode/src/preview.rs#L123) |
|
||||
| FR-DEV-3a | [`core/dr-pipeline/src/graph.rs:14`](../core/dr-pipeline/src/graph.rs#L14), [`core/dr-pipeline/src/graph.rs:32`](../core/dr-pipeline/src/graph.rs#L32), [`core/dr-pipeline/src/graph.rs:88`](../core/dr-pipeline/src/graph.rs#L88) |
|
||||
| FR-DEV-3b | [`core/dr-pipeline/src/graph.rs:32`](../core/dr-pipeline/src/graph.rs#L32) |
|
||||
| FR-DEV-3c | [`core/dr-pipeline/src/graph.rs:88`](../core/dr-pipeline/src/graph.rs#L88) |
|
||||
| FR-DEV-3e | [`core/dr-decode/src/lib.rs:325`](../core/dr-decode/src/lib.rs#L325), [`core/dr-decode/src/lib.rs:439`](../core/dr-decode/src/lib.rs#L439) |
|
||||
| FR-DEV-3 | [`core/dr-pipeline/src/framing.rs:174`](../core/dr-pipeline/src/framing.rs#L174) |
|
||||
| FR-DEV-3a | [`core/dr-pipeline/src/graph.rs:128`](../core/dr-pipeline/src/graph.rs#L128), [`core/dr-pipeline/src/graph.rs:15`](../core/dr-pipeline/src/graph.rs#L15), [`core/dr-pipeline/src/graph.rs:33`](../core/dr-pipeline/src/graph.rs#L33) |
|
||||
| FR-DEV-3b | [`core/dr-pipeline/src/graph.rs:33`](../core/dr-pipeline/src/graph.rs#L33) |
|
||||
| FR-DEV-3c | [`core/dr-pipeline/src/graph.rs:128`](../core/dr-pipeline/src/graph.rs#L128) |
|
||||
| FR-DEV-3d | [`core/dr-pipeline/src/framing.rs:174`](../core/dr-pipeline/src/framing.rs#L174) |
|
||||
| FR-DEV-3e | [`core/dr-decode/src/lib.rs:325`](../core/dr-decode/src/lib.rs#L325), [`core/dr-decode/src/lib.rs:445`](../core/dr-decode/src/lib.rs#L445) |
|
||||
| FR-DEV-4 | [`core/dr-gpu/src/lib.rs:123`](../core/dr-gpu/src/lib.rs#L123) |
|
||||
| FR-DSP-1 | [`ui/dr-ui/src/lib.rs:31`](../ui/dr-ui/src/lib.rs#L31) |
|
||||
| FR-EXP-9 | [`core/dr-decode/src/lib.rs:242`](../core/dr-decode/src/lib.rs#L242) |
|
||||
| FR-NC-1 | [`core/dr-sync-nextcloud/src/auth.rs:132`](../core/dr-sync-nextcloud/src/auth.rs#L132), [`core/dr-sync-nextcloud/src/auth.rs:44`](../core/dr-sync-nextcloud/src/auth.rs#L44) |
|
||||
| FR-NC-12 | [`core/dr-sync-nextcloud/src/lib.rs:32`](../core/dr-sync-nextcloud/src/lib.rs#L32), [`core/dr-sync/src/lib.rs:128`](../core/dr-sync/src/lib.rs#L128), [`core/dr-sync/src/lib.rs:34`](../core/dr-sync/src/lib.rs#L34) |
|
||||
| FR-NC-12 | [`core/dr-sync-nextcloud/src/lib.rs:34`](../core/dr-sync-nextcloud/src/lib.rs#L34), [`core/dr-sync/src/lib.rs:130`](../core/dr-sync/src/lib.rs#L130), [`core/dr-sync/src/lib.rs:36`](../core/dr-sync/src/lib.rs#L36) |
|
||||
| FR-NC-3 | [`core/dr-decode/src/preview.rs:123`](../core/dr-decode/src/preview.rs#L123), [`core/dr-sync/src/capability.rs:41`](../core/dr-sync/src/capability.rs#L41) |
|
||||
| FR-NC-4 | [`core/dr-sync-nextcloud/src/propfind.rs:100`](../core/dr-sync-nextcloud/src/propfind.rs#L100), [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`core/dr-sync/src/capability.rs:6`](../core/dr-sync/src/capability.rs#L6), [`core/dr-sync/src/lib.rs:128`](../core/dr-sync/src/lib.rs#L128) |
|
||||
| FR-NC-4 | [`core/dr-sync-nextcloud/src/propfind.rs:100`](../core/dr-sync-nextcloud/src/propfind.rs#L100), [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51), [`core/dr-sync/src/capability.rs:6`](../core/dr-sync/src/capability.rs#L6), [`core/dr-sync/src/lib.rs:130`](../core/dr-sync/src/lib.rs#L130), [`core/dr-sync/src/scan.rs:50`](../core/dr-sync/src/scan.rs#L50) |
|
||||
| FR-NC-5 | [`core/dr-sync-nextcloud/src/propfind.rs:51`](../core/dr-sync-nextcloud/src/propfind.rs#L51) |
|
||||
| FR-NC-6c | [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-types/src/lib.rs:131`](../core/dr-types/src/lib.rs#L131), [`core/dr-types/src/lib.rs:80`](../core/dr-types/src/lib.rs#L80) |
|
||||
| FR-PLAT-AND-1 | [`core/dr-types/src/lib.rs:23`](../core/dr-types/src/lib.rs#L23) |
|
||||
| FR-RAW-1 | [`core/dr-decode/src/lib.rs:174`](../core/dr-decode/src/lib.rs#L174), [`core/dr-types/src/lib.rs:90`](../core/dr-types/src/lib.rs#L90) |
|
||||
| FR-NC-6a | [`core/dr-types/src/selector.rs:1`](../core/dr-types/src/selector.rs#L1) |
|
||||
| FR-NC-6c | [`core/dr-sync-nextcloud/src/desktop_client.rs:30`](../core/dr-sync-nextcloud/src/desktop_client.rs#L30), [`core/dr-types/src/lib.rs:175`](../core/dr-types/src/lib.rs#L175), [`core/dr-types/src/lib.rs:93`](../core/dr-types/src/lib.rs#L93) |
|
||||
| FR-NC-9 | [`core/dr-catalog/src/merge.rs:1`](../core/dr-catalog/src/merge.rs#L1), [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1) |
|
||||
| FR-PLAT-AND-1 | [`core/dr-types/src/lib.rs:36`](../core/dr-types/src/lib.rs#L36) |
|
||||
| FR-PLAT-AND-3 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1) |
|
||||
| FR-RAW-1 | [`core/dr-decode/src/lib.rs:174`](../core/dr-decode/src/lib.rs#L174), [`core/dr-types/src/lib.rs:103`](../core/dr-types/src/lib.rs#L103), [`core/dr-types/src/lib.rs:174`](../core/dr-types/src/lib.rs#L174) |
|
||||
| FR-RAW-3 | [`core/dr-decode/src/lib.rs:242`](../core/dr-decode/src/lib.rs#L242), [`core/dr-decode/src/lib.rs:70`](../core/dr-decode/src/lib.rs#L70) |
|
||||
| FR-RAW-4 | [`core/dr-decode/src/error.rs:1`](../core/dr-decode/src/error.rs#L1) |
|
||||
| FR-RAW-5 | [`core/dr-decode/src/lib.rs:98`](../core/dr-decode/src/lib.rs#L98) |
|
||||
| FR-UI-1 | [`ui/dr-ui/src/lib.rs:39`](../ui/dr-ui/src/lib.rs#L39) |
|
||||
| FR-UI-2 | [`ui/dr-ui/src/lib.rs:39`](../ui/dr-ui/src/lib.rs#L39) |
|
||||
| NFR-ARCH-2 | [`core/dr-catalog/src/jobs.rs:1`](../core/dr-catalog/src/jobs.rs#L1) |
|
||||
| NFR-ARCH-4 | [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1) |
|
||||
| NFR-OPS-1 | [`tools/traceability/src/lib.rs:266`](../tools/traceability/src/lib.rs#L266) |
|
||||
| NFR-P1 | [`tools/traceability/src/lib.rs:473`](../tools/traceability/src/lib.rs#L473) |
|
||||
| NFR-P1 | [`core/dr-catalog/src/lib.rs:1`](../core/dr-catalog/src/lib.rs#L1), [`core/dr-catalog/src/scan.rs:1`](../core/dr-catalog/src/scan.rs#L1), [`tools/traceability/src/lib.rs:473`](../tools/traceability/src/lib.rs#L473) |
|
||||
| NFR-P13 | [`core/dr-decode/src/preview.rs:96`](../core/dr-decode/src/preview.rs#L96) |
|
||||
| NFR-R1 | [`core/dr-catalog/src/sync.rs:1`](../core/dr-catalog/src/sync.rs#L1) |
|
||||
| NFR-R5 | [`core/dr-catalog/src/error.rs:1`](../core/dr-catalog/src/error.rs#L1), [`core/dr-catalog/src/schema.rs:1`](../core/dr-catalog/src/schema.rs#L1) |
|
||||
| NFR-R7 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) |
|
||||
| NFR-R8 | [`core/dr-gpu/src/error.rs:1`](../core/dr-gpu/src/error.rs#L1) |
|
||||
| NFR-RES-1 | [`ui/dr-ui/src/lib.rs:31`](../ui/dr-ui/src/lib.rs#L31) |
|
||||
@@ -72,7 +85,7 @@ _None._
|
||||
|
||||
## Not yet tagged
|
||||
|
||||
107 of 143 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built.
|
||||
94 of 143 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built.
|
||||
|
||||
<details><summary>Show untagged requirements</summary>
|
||||
|
||||
@@ -81,10 +94,6 @@ _None._
|
||||
- FR-CAT-12
|
||||
- FR-CAT-13
|
||||
- FR-CAT-14
|
||||
- FR-CAT-3
|
||||
- FR-CAT-4
|
||||
- FR-CAT-6
|
||||
- FR-CAT-7
|
||||
- FR-CAT-8
|
||||
- FR-CULL-3
|
||||
- FR-CULL-4
|
||||
@@ -93,8 +102,6 @@ _None._
|
||||
- FR-CULL-7
|
||||
- FR-DEV-1
|
||||
- FR-DEV-2
|
||||
- FR-DEV-3
|
||||
- FR-DEV-3d
|
||||
- FR-DEV-3f
|
||||
- FR-DEV-3g
|
||||
- FR-DEV-5
|
||||
@@ -120,13 +127,10 @@ _None._
|
||||
- FR-NC-11
|
||||
- FR-NC-2
|
||||
- FR-NC-6
|
||||
- FR-NC-6a
|
||||
- FR-NC-6b
|
||||
- FR-NC-7
|
||||
- FR-NC-8
|
||||
- FR-NC-9
|
||||
- FR-PLAT-AND-2
|
||||
- FR-PLAT-AND-3
|
||||
- FR-PLAT-AND-4
|
||||
- FR-PLAT-AND-5
|
||||
- FR-PLAT-AND-6
|
||||
@@ -143,9 +147,7 @@ _None._
|
||||
- NFR-A11Y-2
|
||||
- NFR-A11Y-3
|
||||
- NFR-ARCH-1
|
||||
- NFR-ARCH-2
|
||||
- NFR-ARCH-3
|
||||
- NFR-ARCH-4
|
||||
- NFR-COMPAT-1
|
||||
- NFR-COMPAT-2
|
||||
- NFR-OPS-2
|
||||
@@ -167,11 +169,9 @@ _None._
|
||||
- NFR-PORT-1
|
||||
- NFR-PORT-2
|
||||
- NFR-PORT-3
|
||||
- NFR-R1
|
||||
- NFR-R2
|
||||
- NFR-R3
|
||||
- NFR-R4
|
||||
- NFR-R5
|
||||
- NFR-R6
|
||||
- NFR-RES-2
|
||||
- NFR-RES-3
|
||||
|
||||
Reference in New Issue
Block a user