Bring the outstanding register up to 0.16.0

Five things in it were no longer true of the tree:

- FR-DSP-4 said "Unbuilt" and that nothing tracked a provisional frame.
  0.15.0 finished it: the settle debounce in ui/dr-ui/src/refine.rs,
  the draft flag reaching the histogram (canvas-draft,
  Levels.provisional), and the 150 ms fade of the last draft
  (app.slint's canvas-previous).
- The note that dedup.rs is re-import detection stood alone. FR-CAT-11a
  is now built beside it: dr_catalog::duplicates, dr_ui::duplicates and
  the Duplicate originals review.
- Culling counted three unbuilt clauses and named two.
- Section 6 still counted zero @tr( and five accessible-* lines, figures
  from before 2026-08-30. The launch screen is converted (build.rs holds
  the mechanism, no .po exists), 127 accessible-* lines sit across eleven
  files, and ui_controls_are_accessible.rs holds the structure.
- Section 11 said nothing of the panorama was built. It was, the same
  week; FR-MRG-9, NFR-MRG-1 and NFR-MRG-2 are what carry no tag.

A new section 4a lists what the develop, mask and keyboard work left
open, so that its closed clauses are not looked for: FR-UI-5's wheel on
sliders, FR-RAW-2's second decoder, mask-editing M2's remainder, and
FR-DEV-17's deliberate blind spot for range and region parts. The
Android paragraph now says develop is zero-copy there since TD-1 was
paid off, and that the APK carries the manual.
This commit is contained in:
2026-09-26 07:45:00 -04:00
parent ea44aba110
commit 79c051e8a6
+93 -33
View File
@@ -28,6 +28,12 @@ Android memory pressure and lost-root recovery (FR-PLAT-AND-5, FR-PLAT-AND-2), i
recorded where it appears rather than deleted, because a requirement that is *half* met is the recorded where it appears rather than deleted, because a requirement that is *half* met is the
one most likely to be reported as closed. one most likely to be reported as closed.
**Swept again on 2026-09-26, for 0.16.0.** Progressive refinement (FR-DSP-4) and the panorama
(§3.11) were built and are struck below; consolidating duplicate originals (FR-CAT-11a) now sits
beside re-import detection; the accessibility and localisation counts in §6 had not been read
against the tree since 2026-08-30 and are replaced; and §4a records what the develop and keyboard
work of 0.15.0 and 0.16.0 left open.
--- ---
## 1. Plugins — post-v1 since 2026-09-19 ## 1. Plugins — post-v1 since 2026-09-19
@@ -80,8 +86,8 @@ somebody reads the matrix.
## 2. Culling — the stated differentiator, half built ## 2. Culling — the stated differentiator, half built
[D11](requirements.md) names culling "the core differentiator". FR-CULL-1, -2, -3, -4 and -8 through [D11](requirements.md) names culling "the core differentiator". FR-CULL-1 through -5 and -8 through
-12 are built. Three are not. -13 are built. Two are not.
**FR-CULL-3 — Raw-truth overlays. Built, all three bullets.** Focus peaking is **FR-CULL-3 — Raw-truth overlays. Built, all three bullets.** Focus peaking is
`core/dr-gpu/src/focus.rs` and `ui/dr-ui/src/peaking.rs`; the raw histogram and the raw clipping `core/dr-gpu/src/focus.rs` and `ui/dr-ui/src/peaking.rs`; the raw histogram and the raw clipping
@@ -115,8 +121,13 @@ labels — that comment was a forward reference and is now simply wrong, rather
And `dr_catalog::bursts::choose_representative` is written and tested but bound to no gesture, so And `dr_catalog::bursts::choose_representative` is written and tested but bound to no gesture, so
today the only override is expanding the burst. today the only override is expanding the burst.
`core/dr-catalog/src/dedup.rs` remains a different thing: re-import detection under FR-CAT-11, Neither is deduplication, which is about one file held twice rather than two frames that look
matching a file against one already catalogued, not two photographs against each other. alike. `core/dr-catalog/src/dedup.rs` is re-import detection under FR-CAT-11, matching a file on
a card against one already catalogued. Its other half, FR-CAT-11a, is built since 0.16.0:
`core/dr-catalog/src/duplicates.rs` groups the copies a library already holds (same root, camera,
capture instant and size), `ui/dr-ui/src/duplicates.rs` proves each group the same by digest and
compares their edits, and a group is folded onto one survivor with the others trashed in one
transaction, from the Duplicate originals review in the sidebar and in Settings.
**FR-CULL-6 — Compare and survey.** Absent. No side-by-side view, no synchronised zoom or pan. **FR-CULL-6 — Compare and survey.** Absent. No side-by-side view, no synchronised zoom or pan.
This is the one of the four with no adjacent machinery at all, and it is also the one that most This is the one of the four with no adjacent machinery at all, and it is also the one that most
@@ -166,10 +177,15 @@ Two measurements say it costs more than it saves on the interactive path. Neithe
about the export path or about a device under memory pressure, which is where the case for it about the export path or about a device under memory pressure, which is where the case for it
actually lives — and that is spike S6, which has not run. actually lives — and that is spike S6, which has not run.
**FR-DSP-4 — Progressive refinement.** Unbuilt. FR-DSP-1's proxy rendering and TD-4's **FR-DSP-4 — Progressive refinement. Built in 0.15.0.** While a gesture moves the canvas renders
quarter-resolution base are adjacent and are not it: both are fixed choices about what resolution to a half-resolution draft, and the sharp frame lands once, 120 ms after the last movement: the
compute at, where FR-DSP-4 asks for a first frame that is deliberately cheap and a second that decision is `ui/dr-ui/src/refine.rs`, a debounce whose every draft re-arms the settle timer, driven
replaces it. Nothing tracks a "this frame is provisional" state. through simulated timelines in its tests. The draft flag reaches the interface (`canvas-draft`,
`Levels.provisional`), so the histogram dims while it describes a frame older than the one on
screen, and the last draft is kept and faded out over 150 ms when the sharp frame arrives, so
refinement is not a swap. Draft frames themselves date from 2026-08-09; what this entry missed,
and 0.15.0 finished, was a settle that waited for the gesture to stop, a provisional state the
interface could see, and a refinement that was not a jarring swap.
**NFR-RES-2 — Images larger than GPU memory.** Half answered. NFR-R8's "decide explicitly" was **NFR-RES-2 — Images larger than GPU memory.** Half answered. NFR-R8's "decide explicitly" was
decided on 2026-09-19: there is no CPU render pipeline, the degraded mode is the viewer on decided on 2026-09-19: there is no CPU render pipeline, the degraded mode is the viewer on
@@ -181,11 +197,42 @@ settle both this and FR-DSP-2, and there is no evidence it has run.
--- ---
## 4a. Develop, masks and the keyboard — what the 0.15.0 and 0.16.0 work left open
Most of what these two releases built closed a clause outright, and is listed here only so that
nobody looks for it below: perspective correction (FR-DEV-20, Vertical and Horizontal in Compose),
the crop that says when it orphans a mask (FR-DEV-17), intersection as a third mask join
(FR-DEV-19a), the keyboard vocabulary for develop (FR-DEV-16, met and held by `gestures-check` in
both directions), colour labels set, shown and filtered (NFR-A11Y-3's first example), the RAW
decoder behind a trait (FR-RAW-2), and duplicate originals (FR-CAT-11a, §2 above). What they left:
- **FR-UI-5's pointer half.** Keys cover navigation, rating and common adjustments in every view,
and judging works in develop on the open photograph; but no develop slider takes the scroll
wheel, which the clause names. Its status note in the register says so rather than rounding up.
- **FR-RAW-2's second decoder.** The trait is built and a stub decoder is proved to reach the
scan, the preview ladder and export without a caller changing; LibRaw itself (D2) is not
built, and S7 is what would say when it is needed.
- **FR-DEV-19's M2 remainder** ([mask-editing.md §11](mask-editing.md)). The panel's Add, Subtract
and Intersect buttons join a painted part only, so an intersection with a gradient or a range is
painted where the mask should survive; per-part distance fields and folding two layers are
unbuilt. From M1, the brush has no ring drawn on the photograph and a layer's whole stroke
history is redrawn per dab.
- **FR-DEV-17's blind spot, by design.** A layer that adds a range or a region selection is never
reported, because the check has no label map to measure it with (`dr_pipeline::orphan`); a
false alarm on the common path would teach the notice to be dismissed unread.
The desktop's scrollbars (the develop column, the grid, the sidebar, Settings, the film list and
the help sheet) are drawn only where `dr_plat::is_touch_first()` is false; on Android the lists
still scroll by flick alone, which is deliberate rather than outstanding.
---
## 5. Android beyond running, and Flatpak ## 5. Android beyond running, and Flatpak
The Android app is not a stub — it builds an APK, runs the whole application, unpacks bundled face The Android app is not a stub — it builds an APK, runs the whole application, unpacks bundled face
models, and has been measured on a tablet ([faces.md §12.1](faces.md), models, has been measured on a tablet ([faces.md §12.1](faces.md)), and since 0.15.0 draws the
[technical-debt.md TD-1](technical-debt.md)). What is missing is the platform contract around it. develop view zero-copy as the desktop does ([technical-debt.md TD-1](technical-debt.md), paid off).
It carries the manual and opens it in a WebView. What is missing is the platform contract around it.
**FR-PLAT-AND-1 is untagged, and what it was tagged for was intent rather than code.** **FR-PLAT-AND-1 is untagged, and what it was tagged for was intent rather than code.**
The requirement demands that library access be obtained *exclusively* through the Storage Access The requirement demands that library access be obtained *exclusively* through the Storage Access
@@ -267,21 +314,29 @@ fixes users will need — is unaddressed, and there is no update mechanism of an
## 6. Accessibility and internationalisation — the hard half is done and the easy half is not ## 6. Accessibility and internationalisation — the hard half is done and the easy half is not
**NFR-A11Y-1 — Localisation.** `@tr(` appears **zero** times across 14,482 lines of Slint. That **NFR-A11Y-1 — Localisation.** One screen of twenty-eight Slint files is converted: the launch
number overstates the problem, because the part that is genuinely architectural was got right: screen wraps its strings in `@tr(`, and `ui/dr-ui/build.rs` records the mechanism — Slint's bundled
`LocalizedKey` keeps display strings out of `core/` entirely, every operation publishes a key rather translations, a `.po` per language under `ui/dr-ui/lang/`, extracted with `slint-tr-extractor` —
than a label, and `labels::resolve` is the single point where a key becomes text. What that single and why bundling rather than gettext (Android has no path a `.mo` could sit at). No `.po` exists
point does, however, is a hardcoded English `match` in Rust source — so changing a translation yet, so every build is the original English, and the rest of the interface (about 23,600 lines of
requires a recompile, which is the one thing the requirement explicitly forbids. There is no message markup) is unwrapped. The part that is genuinely architectural was got right: `LocalizedKey` keeps
catalogue in any format, no locale-resolution rule, and no decision recorded about RTL. display strings out of `core/` entirely, every operation publishes a key rather than a label, and
`labels.rs` is the single point where a key becomes text — but that point is a hardcoded English
`match`, and `build.rs` says the Rust-side mechanism is not built. Two things the requirement asks
remain against it even when the conversion is finished: a bundled translation is compiled in, so
changing one still needs a rebuild, which the clause forbids; and there is no locale-resolution rule
and no decision recorded about RTL.
The work left is therefore smaller than it looks and entirely mechanical: a catalogue format, a load **NFR-A11Y-2 — Accessibility. Tagged, and half tested.** `accessible-*` properties now appear on
path behind `resolve`, and `@tr(` around the Slint literals. The design it needs already exists. some 127 lines across eleven Slint files — the shared controls in `widgets.slint` and
`controls.slint`, every slider, the rail, the grid's cells (named by file), the rating stars, the
**NFR-A11Y-2 — Accessibility.** `accessible-*` appears five times in the whole interface, all five labels and the duplicates review — and `ui/dr-ui/tests/ui_controls_are_accessible.rs` fails when a
on one control — the parameter slider in `adjust.slint` — and nothing is set from the Rust side at shared control stops declaring its role or a slider is added without a name. The manual's recording
all. Everything else in eighteen Slint files is unnamed to AT-SPI and TalkBack. The requirement's own scripts find every control they press through the same names (`dr_ui::automation`), so a control a
caveat, that Slint's Android accessibility needs verifying, is spike S13, which has not run. screen reader cannot name is also one the manual cannot record. What is unchecked is stated in the
test: whether the words are good, and whether Android exposes any of it — spike S13, which has not
run. The requirement's other two clauses are debt with entries of their own: platform font scaling
([TD-7](technical-debt.md)) and WCAG AA contrast ([TD-6](technical-debt.md)).
**NFR-A11Y-3 — Colour-independent status.** Built, and now asserted. The clipping readout pairs a **NFR-A11Y-3 — Colour-independent status.** Built, and now asserted. The clipping readout pairs a
marker that appears or disappears with a figure in words; the rating strip is a solid star against marker that appears or disappears with a figure in words; the rating strip is a solid star against
@@ -453,18 +508,23 @@ whether something *should* be built — which is the opposite of the order §9 a
--- ---
## 11. Merging — specified 2026-09-19, nothing built ## 11. Merging — the panorama built, three clauses open
§3.11 was written on 2026-09-19 under D18, undeferring the panorama from §7 and leaving HDR merge §3.11 was written on 2026-09-19 under D18, undeferring the panorama from §7 and leaving HDR merge
and focus stacking there with their data model decided. Eleven `FR-MRG` clauses and two `NFR-MRG` and focus stacking there with their data model decided. Its clauses entered the register with no
figures entered the register at once with no code behind any of them, which is why the coverage code behind them, which is why the coverage figure fell from 83.0% to 77.2% on that day — and the
figure fell from 83.0% to 77.2% on the same day — a specification, not a regression. panorama was then built in the same week: alignment, projections, the chunked composite written as
a DNG beside its sources, the auto-crop and the model's border fill.
[panorama.md](panorama.md) §11 and §13 are where it stands.
[panorama.md](panorama.md) is the design, and its §10 is the order of work. Nothing starts before Three clauses carry no tag:
**S15**: whether rawler reads back a linear DNG the application writes, whether XFeat loads under
tract at a fixed shape, whether the working-space texture can be tapped where FR-MRG-2 needs it, - **FR-MRG-9 — the tablet.** Android runs the same code, but the stated ceiling on frame count and
and what a chunked blend of a 100 MP composite costs on the tablet. The first two are a day each source resolution, refused with a message rather than an out-of-memory kill, is not set.
and either can change the design, which is the reason they come first. - **NFR-MRG-1 — merge latency.** Measured on the desktop and inside its 60 s (panorama.md §11);
the tablet's figure is S15.4's open half, and nothing asserts either per commit.
- **NFR-MRG-2 — reproducible merges.** Nothing checks that the same sources and settings give a
byte-identical composite.
## 12. D12, which governed all of the above ## 12. D12, which governed all of the above