diff --git a/.gitea/workflows/traceability-check.yml b/.gitea/workflows/traceability-check.yml
index 68e7ba0..1b9762f 100644
--- a/.gitea/workflows/traceability-check.yml
+++ b/.gitea/workflows/traceability-check.yml
@@ -82,6 +82,19 @@ jobs:
exit 1
fi
+ # The gesture vocabulary, from the same scanner and under the same rule.
+ #
+ # Blocking, and for a sharper reason than the matrix: these two artefacts
+ # are not only read, one of them is *shown to the user*. A stale
+ # `gesture_book.rs` is a help sheet in the application telling somebody to
+ # perform a gesture that was removed — worse than no help sheet, because
+ # they will conclude the application is broken rather than the page.
+ #
+ # This also fails on a malformed tag, so a typo costs a gesture its
+ # desktop half loudly rather than silently.
+ - name: Regenerate the gesture vocabulary and check it is committed
+ run: cargo run -q -p traceability -- gestures-check
+
# Advisory, not blocking: not every file implements a requirement, and a
# tag on every function is noise that rots faster than it helps. Tag the
# unit that decides.
diff --git a/.githooks/pre-commit b/.githooks/pre-commit
index f713676..32957bf 100755
--- a/.githooks/pre-commit
+++ b/.githooks/pre-commit
@@ -1,5 +1,10 @@
#!/usr/bin/env bash
-# Keep docs/traceability.md in step with the tags in the tree.
+# Keep the generated artefacts in step with the tags in the tree.
+#
+# Two of them now, from the same scanner: the requirements matrix and the
+# gesture vocabulary. Both are generated *from* the tree and cite line numbers
+# in it, so both go stale on any commit that moves a line — a `cargo fmt` sweep
+# above all, but equally a commit that merely adds a paragraph above a tag.
#
# The gate regenerates the matrix in CI and fails if the result differs from
# what is committed. That is the right check — a matrix that disagrees with the
@@ -18,11 +23,13 @@ staged="$(git diff --cached --name-only --diff-filter=ACMR)"
if ! grep -qE '\.(rs|slint|yaml|md)$' <<< "${staged}"; then
exit 0
fi
-# The matrix is generated from the tree, so regenerating it because it was
-# itself edited would be circular.
-if [ "$(tr -d '[:space:]' <<< "${staged}")" = "docs/traceability.md" ]; then
- exit 0
-fi
+# The artefacts are generated from the tree, so regenerating them because one
+# was itself edited would be circular.
+case "$(tr -d '[:space:]' <<< "${staged}")" in
+ docs/traceability.md | docs/gestures.md | ui/dr-ui/src/gesture_book.rs)
+ exit 0
+ ;;
+esac
repo="$(git rev-parse --show-toplevel)"
cd "${repo}"
@@ -38,3 +45,23 @@ if ! git diff --quiet -- docs/traceability.md; then
git add docs/traceability.md
echo "pre-commit: regenerated docs/traceability.md and staged it"
fi
+
+# The gesture vocabulary, same discipline.
+#
+# **Failure here is reported and not swallowed**, unlike the matrix above. A
+# matrix that will not build leaves the previous one in place, which is merely
+# stale; a malformed `GESTURE:` block means a gesture the user is about to be
+# told about in the wrong words, or not at all. The gate would catch it in CI
+# either way — this is only about catching it a push earlier.
+if ! out="$(cargo run -q -p traceability -- gestures 2>&1)"; then
+ echo "pre-commit: the gesture scan failed — the tags below need fixing" >&2
+ echo "${out}" >&2
+ exit 1
+fi
+
+for f in docs/gestures.md ui/dr-ui/src/gesture_book.rs; do
+ if ! git diff --quiet -- "${f}"; then
+ git add "${f}"
+ echo "pre-commit: regenerated ${f} and staged it"
+ fi
+done
diff --git a/docs/gestures.md b/docs/gestures.md
index 3e4f314..3ae67c8 100644
--- a/docs/gestures.md
+++ b/docs/gestures.md
@@ -54,7 +54,7 @@ The right match confidence is a property of your library, not of the model. "Wha
Touch has no ctrl, so without a mode there is no way to select a second photograph — the first tap would open it. The hold is the fast way in and the button is the one that can be found.
-`ui/dr-ui/ui/library.slint:1259`
+`ui/dr-ui/ui/library.slint:1282`
### Add or remove one photograph
@@ -63,7 +63,7 @@ Touch has no ctrl, so without a mode there is no way to select a second photogra
While selecting, a tap never opens. That is the whole point of the mode: one meaning per gesture at a time. Press Done to get tap-to-open back.
-`ui/dr-ui/ui/library.slint:1268`
+`ui/dr-ui/ui/library.slint:1291`
### Leave selecting
@@ -71,7 +71,7 @@ While selecting, a tap never opens. That is the whole point of the mode: one mea
- **Pointer** — Press Done in the header
- **Keyboard** — Escape
-`ui/dr-ui/ui/library.slint:1276`
+`ui/dr-ui/ui/library.slint:1299`
### Select a range
@@ -80,7 +80,7 @@ While selecting, a tap never opens. That is the whole point of the mode: one mea
This replaced a double tap, which had no visible state and could take forty photographs by accident. The run is resolved by the catalog rather than by what is on screen, so the grid can scroll between the two taps — the ranges that hurt on a tablet are longer than a screenful, which is exactly where a finger sweep runs out.
-`ui/dr-ui/ui/library.slint:1337`
+`ui/dr-ui/ui/library.slint:1360`
### Find photographs with two people in them
@@ -89,7 +89,7 @@ This replaced a double tap, which had no visible state and could take forty phot
"Any of them" is a union and "all of them" is an intersection. The tray is where both terms and the choice between them live, because a filter belongs on the filter bar.
-`ui/dr-ui/ui/library.slint:2076`
+`ui/dr-ui/ui/library.slint:2101`
### Drop the selection but keep selecting
@@ -98,7 +98,7 @@ This replaced a double tap, which had no visible state and could take forty phot
Distinct from Done, which leaves the mode entirely. Clearing keeps it, so the next selection can start straight away.
-`ui/dr-ui/ui/library.slint:2367`
+`ui/dr-ui/ui/library.slint:2392`
### Select everything the grid is showing
@@ -107,7 +107,7 @@ Distinct from Done, which leaves the mode entirely. Clearing keeps it, so the ne
A scoped grid of two hundred frames is two hundred taps otherwise, and "all of them, except those three" is a far more common shape than the taps it took to say it.
-`ui/dr-ui/ui/library.slint:2384`
+`ui/dr-ui/ui/library.slint:2409`
### Resize the thumbnails
@@ -116,7 +116,7 @@ A scoped grid of two hundred frames is two hundred taps otherwise, and "all of t
There is no wheel on a tablet, so without the pinch the cell size could only be changed by a control a finger cannot reach.
-`ui/dr-ui/ui/library.slint:2825`
+`ui/dr-ui/ui/library.slint:2850`
### File photographs in a collection
@@ -125,7 +125,7 @@ There is no wheel on a tablet, so without the pinch the cell size could only be
The selection is what the drag carries, which is why selecting several is worth the mode: forty photographs file in one gesture.
-`ui/dr-ui/ui/library.slint:2974`
+`ui/dr-ui/ui/library.slint:2999`
### Open a photograph
@@ -134,7 +134,7 @@ The selection is what the drag carries, which is why selecting several is worth
A tap opens; a tap that *moved* does not. Travel is what separates a deliberate tap from a hand brushing past, and it is the only thing that does: the two are the same length. An earlier version required the finger to dwell 120 ms instead, and that rejected ordinary taps — a real tap is often quicker than a brush.
-`ui/dr-ui/ui/library.slint:3204`
+`ui/dr-ui/ui/library.slint:3229`
### Rate a photograph without opening it
@@ -144,4 +144,4 @@ A tap opens; a tap that *moved* does not. Travel is what separates a deliberate
A star has to take the press without it also reaching the cell, or every rating throws the user into develop.
-`ui/dr-ui/ui/library.slint:3316`
+`ui/dr-ui/ui/library.slint:3341`
diff --git a/docs/traceability.md b/docs/traceability.md
index a74d15c..300262d 100644
--- a/docs/traceability.md
+++ b/docs/traceability.md
@@ -121,7 +121,7 @@ _None._
| FR-UI-1 | [`ui/dr-ui/src/lib.rs:3172`](../ui/dr-ui/src/lib.rs#L3172), [`ui/dr-ui/src/lib.rs:89`](../ui/dr-ui/src/lib.rs#L89), [`ui/dr-ui/src/masks_ui.rs:816`](../ui/dr-ui/src/masks_ui.rs#L816), [`ui/dr-ui/ui/identity.slint:181`](../ui/dr-ui/ui/identity.slint#L181), [`ui/dr-ui/ui/library.slint:1099`](../ui/dr-ui/ui/library.slint#L1099) |
| FR-UI-2 | [`ui/dr-ui/src/collections_ui.rs:1002`](../ui/dr-ui/src/collections_ui.rs#L1002), [`ui/dr-ui/src/collections_ui.rs:1012`](../ui/dr-ui/src/collections_ui.rs#L1012), [`ui/dr-ui/src/collections_ui.rs:119`](../ui/dr-ui/src/collections_ui.rs#L119), [`ui/dr-ui/src/collections_ui.rs:1495`](../ui/dr-ui/src/collections_ui.rs#L1495), [`ui/dr-ui/src/collections_ui.rs:1509`](../ui/dr-ui/src/collections_ui.rs#L1509), [`ui/dr-ui/src/collections_ui.rs:160`](../ui/dr-ui/src/collections_ui.rs#L160), [`ui/dr-ui/src/collections_ui.rs:1761`](../ui/dr-ui/src/collections_ui.rs#L1761), [`ui/dr-ui/src/collections_ui.rs:2812`](../ui/dr-ui/src/collections_ui.rs#L2812), [`ui/dr-ui/src/collections_ui.rs:486`](../ui/dr-ui/src/collections_ui.rs#L486), [`ui/dr-ui/src/gestures.rs:1`](../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/lib.rs:89`](../ui/dr-ui/src/lib.rs#L89), [`ui/dr-ui/src/lib.rs:96`](../ui/dr-ui/src/lib.rs#L96), [`ui/dr-ui/src/library_ui.rs:308`](../ui/dr-ui/src/library_ui.rs#L308), [`ui/dr-ui/src/library_ui.rs:5394`](../ui/dr-ui/src/library_ui.rs#L5394), [`ui/dr-ui/src/library_ui.rs:5539`](../ui/dr-ui/src/library_ui.rs#L5539), [`ui/dr-ui/src/library_ui.rs:6744`](../ui/dr-ui/src/library_ui.rs#L6744), [`ui/dr-ui/ui/adjust.slint:1072`](../ui/dr-ui/ui/adjust.slint#L1072), [`ui/dr-ui/ui/adjust.slint:544`](../ui/dr-ui/ui/adjust.slint#L544), [`ui/dr-ui/ui/adjust.slint:741`](../ui/dr-ui/ui/adjust.slint#L741), [`ui/dr-ui/ui/app.slint:2119`](../ui/dr-ui/ui/app.slint#L2119), [`ui/dr-ui/ui/app.slint:530`](../ui/dr-ui/ui/app.slint#L530), [`ui/dr-ui/ui/app.slint:537`](../ui/dr-ui/ui/app.slint#L537), [`ui/dr-ui/ui/app.slint:58`](../ui/dr-ui/ui/app.slint#L58), [`ui/dr-ui/ui/app.slint:888`](../ui/dr-ui/ui/app.slint#L888), [`ui/dr-ui/ui/controls.slint:474`](../ui/dr-ui/ui/controls.slint#L474), [`ui/dr-ui/ui/develop.slint:223`](../ui/dr-ui/ui/develop.slint#L223), [`ui/dr-ui/ui/gestures.slint:1`](../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/histogram.slint:127`](../ui/dr-ui/ui/histogram.slint#L127), [`ui/dr-ui/ui/history.slint:118`](../ui/dr-ui/ui/history.slint#L118), [`ui/dr-ui/ui/library.slint:1267`](../ui/dr-ui/ui/library.slint#L1267), [`ui/dr-ui/ui/library.slint:1274`](../ui/dr-ui/ui/library.slint#L1274), [`ui/dr-ui/ui/library.slint:1337`](../ui/dr-ui/ui/library.slint#L1337), [`ui/dr-ui/ui/library.slint:2692`](../ui/dr-ui/ui/library.slint#L2692), [`ui/dr-ui/ui/library.slint:3828`](../ui/dr-ui/ui/library.slint#L3828), [`ui/dr-ui/ui/library.slint:850`](../ui/dr-ui/ui/library.slint#L850), [`ui/dr-ui/ui/library.slint:906`](../ui/dr-ui/ui/library.slint#L906), [`ui/dr-ui/ui/masks.slint:314`](../ui/dr-ui/ui/masks.slint#L314), [`ui/dr-ui/ui/peaking.slint:82`](../ui/dr-ui/ui/peaking.slint#L82), [`ui/dr-ui/ui/presets.slint:19`](../ui/dr-ui/ui/presets.slint#L19), [`ui/dr-ui/ui/settings.slint:110`](../ui/dr-ui/ui/settings.slint#L110), [`ui/dr-ui/ui/spots.slint:88`](../ui/dr-ui/ui/spots.slint#L88) |
| FR-UI-3 | [`ui/dr-ui/src/develop.rs:2224`](../ui/dr-ui/src/develop.rs#L2224), [`ui/dr-ui/src/develop.rs:2333`](../ui/dr-ui/src/develop.rs#L2333), [`ui/dr-ui/src/library_ui.rs:4503`](../ui/dr-ui/src/library_ui.rs#L4503), [`ui/dr-ui/src/masks_ui.rs:218`](../ui/dr-ui/src/masks_ui.rs#L218), [`ui/dr-ui/src/masks_ui.rs:908`](../ui/dr-ui/src/masks_ui.rs#L908), [`ui/dr-ui/src/masks_ui.rs:930`](../ui/dr-ui/src/masks_ui.rs#L930), [`ui/dr-ui/src/spots_ui.rs:19`](../ui/dr-ui/src/spots_ui.rs#L19), [`ui/dr-ui/ui/app.slint:1935`](../ui/dr-ui/ui/app.slint#L1935), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4), [`ui/dr-ui/ui/collections.slint:682`](../ui/dr-ui/ui/collections.slint#L682), [`ui/dr-ui/ui/masks.slint:524`](../ui/dr-ui/ui/masks.slint#L524) |
-| FR-UI-4 | [`tools/traceability/src/gestures.rs:1`](../tools/traceability/src/gestures.rs#L1), [`tools/traceability/src/gestures.rs:365`](../tools/traceability/src/gestures.rs#L365), [`ui/dr-ui/src/collections_ui.rs:1002`](../ui/dr-ui/src/collections_ui.rs#L1002), [`ui/dr-ui/src/collections_ui.rs:1012`](../ui/dr-ui/src/collections_ui.rs#L1012), [`ui/dr-ui/src/collections_ui.rs:119`](../ui/dr-ui/src/collections_ui.rs#L119), [`ui/dr-ui/src/collections_ui.rs:132`](../ui/dr-ui/src/collections_ui.rs#L132), [`ui/dr-ui/src/collections_ui.rs:1495`](../ui/dr-ui/src/collections_ui.rs#L1495), [`ui/dr-ui/src/collections_ui.rs:1509`](../ui/dr-ui/src/collections_ui.rs#L1509), [`ui/dr-ui/src/collections_ui.rs:160`](../ui/dr-ui/src/collections_ui.rs#L160), [`ui/dr-ui/src/collections_ui.rs:1625`](../ui/dr-ui/src/collections_ui.rs#L1625), [`ui/dr-ui/src/collections_ui.rs:1761`](../ui/dr-ui/src/collections_ui.rs#L1761), [`ui/dr-ui/src/collections_ui.rs:1788`](../ui/dr-ui/src/collections_ui.rs#L1788), [`ui/dr-ui/src/collections_ui.rs:1887`](../ui/dr-ui/src/collections_ui.rs#L1887), [`ui/dr-ui/src/collections_ui.rs:2812`](../ui/dr-ui/src/collections_ui.rs#L2812), [`ui/dr-ui/src/collections_ui.rs:486`](../ui/dr-ui/src/collections_ui.rs#L486), [`ui/dr-ui/src/collections_ui.rs:560`](../ui/dr-ui/src/collections_ui.rs#L560), [`ui/dr-ui/src/gesture_book.rs:3`](../ui/dr-ui/src/gesture_book.rs#L3), [`ui/dr-ui/src/gestures.rs:1`](../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/library_ui.rs:4503`](../ui/dr-ui/src/library_ui.rs#L4503), [`ui/dr-ui/src/library_ui.rs:4515`](../ui/dr-ui/src/library_ui.rs#L4515), [`ui/dr-ui/src/library_ui.rs:4567`](../ui/dr-ui/src/library_ui.rs#L4567), [`ui/dr-ui/src/library_ui.rs:4670`](../ui/dr-ui/src/library_ui.rs#L4670), [`ui/dr-ui/src/library_ui.rs:4698`](../ui/dr-ui/src/library_ui.rs#L4698), [`ui/dr-ui/src/library_ui.rs:5527`](../ui/dr-ui/src/library_ui.rs#L5527), [`ui/dr-ui/src/library_ui.rs:5539`](../ui/dr-ui/src/library_ui.rs#L5539), [`ui/dr-ui/ui/app.slint:1775`](../ui/dr-ui/ui/app.slint#L1775), [`ui/dr-ui/ui/app.slint:506`](../ui/dr-ui/ui/app.slint#L506), [`ui/dr-ui/ui/app.slint:537`](../ui/dr-ui/ui/app.slint#L537), [`ui/dr-ui/ui/app.slint:622`](../ui/dr-ui/ui/app.slint#L622), [`ui/dr-ui/ui/gestures.slint:1`](../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1075`](../ui/dr-ui/ui/library.slint#L1075), [`ui/dr-ui/ui/library.slint:1242`](../ui/dr-ui/ui/library.slint#L1242), [`ui/dr-ui/ui/library.slint:1267`](../ui/dr-ui/ui/library.slint#L1267), [`ui/dr-ui/ui/library.slint:1274`](../ui/dr-ui/ui/library.slint#L1274), [`ui/dr-ui/ui/library.slint:1330`](../ui/dr-ui/ui/library.slint#L1330), [`ui/dr-ui/ui/library.slint:1337`](../ui/dr-ui/ui/library.slint#L1337), [`ui/dr-ui/ui/library.slint:2692`](../ui/dr-ui/ui/library.slint#L2692), [`ui/dr-ui/ui/library.slint:3828`](../ui/dr-ui/ui/library.slint#L3828), [`ui/dr-ui/ui/library.slint:850`](../ui/dr-ui/ui/library.slint#L850), [`ui/dr-ui/ui/library.slint:900`](../ui/dr-ui/ui/library.slint#L900), [`ui/dr-ui/ui/library.slint:906`](../ui/dr-ui/ui/library.slint#L906), [`ui/dr-ui/ui/library.slint:925`](../ui/dr-ui/ui/library.slint#L925) |
+| FR-UI-4 | [`tools/traceability/src/gestures.rs:1`](../tools/traceability/src/gestures.rs#L1), [`tools/traceability/src/gestures.rs:366`](../tools/traceability/src/gestures.rs#L366), [`ui/dr-ui/src/collections_ui.rs:1002`](../ui/dr-ui/src/collections_ui.rs#L1002), [`ui/dr-ui/src/collections_ui.rs:1012`](../ui/dr-ui/src/collections_ui.rs#L1012), [`ui/dr-ui/src/collections_ui.rs:119`](../ui/dr-ui/src/collections_ui.rs#L119), [`ui/dr-ui/src/collections_ui.rs:132`](../ui/dr-ui/src/collections_ui.rs#L132), [`ui/dr-ui/src/collections_ui.rs:1495`](../ui/dr-ui/src/collections_ui.rs#L1495), [`ui/dr-ui/src/collections_ui.rs:1509`](../ui/dr-ui/src/collections_ui.rs#L1509), [`ui/dr-ui/src/collections_ui.rs:160`](../ui/dr-ui/src/collections_ui.rs#L160), [`ui/dr-ui/src/collections_ui.rs:1625`](../ui/dr-ui/src/collections_ui.rs#L1625), [`ui/dr-ui/src/collections_ui.rs:1761`](../ui/dr-ui/src/collections_ui.rs#L1761), [`ui/dr-ui/src/collections_ui.rs:1788`](../ui/dr-ui/src/collections_ui.rs#L1788), [`ui/dr-ui/src/collections_ui.rs:1887`](../ui/dr-ui/src/collections_ui.rs#L1887), [`ui/dr-ui/src/collections_ui.rs:2812`](../ui/dr-ui/src/collections_ui.rs#L2812), [`ui/dr-ui/src/collections_ui.rs:486`](../ui/dr-ui/src/collections_ui.rs#L486), [`ui/dr-ui/src/collections_ui.rs:560`](../ui/dr-ui/src/collections_ui.rs#L560), [`ui/dr-ui/src/gesture_book.rs:3`](../ui/dr-ui/src/gesture_book.rs#L3), [`ui/dr-ui/src/gestures.rs:1`](../ui/dr-ui/src/gestures.rs#L1), [`ui/dr-ui/src/library_ui.rs:4503`](../ui/dr-ui/src/library_ui.rs#L4503), [`ui/dr-ui/src/library_ui.rs:4515`](../ui/dr-ui/src/library_ui.rs#L4515), [`ui/dr-ui/src/library_ui.rs:4567`](../ui/dr-ui/src/library_ui.rs#L4567), [`ui/dr-ui/src/library_ui.rs:4670`](../ui/dr-ui/src/library_ui.rs#L4670), [`ui/dr-ui/src/library_ui.rs:4698`](../ui/dr-ui/src/library_ui.rs#L4698), [`ui/dr-ui/src/library_ui.rs:5527`](../ui/dr-ui/src/library_ui.rs#L5527), [`ui/dr-ui/src/library_ui.rs:5539`](../ui/dr-ui/src/library_ui.rs#L5539), [`ui/dr-ui/ui/app.slint:1775`](../ui/dr-ui/ui/app.slint#L1775), [`ui/dr-ui/ui/app.slint:506`](../ui/dr-ui/ui/app.slint#L506), [`ui/dr-ui/ui/app.slint:537`](../ui/dr-ui/ui/app.slint#L537), [`ui/dr-ui/ui/app.slint:622`](../ui/dr-ui/ui/app.slint#L622), [`ui/dr-ui/ui/gestures.slint:1`](../ui/dr-ui/ui/gestures.slint#L1), [`ui/dr-ui/ui/library.slint:1075`](../ui/dr-ui/ui/library.slint#L1075), [`ui/dr-ui/ui/library.slint:1242`](../ui/dr-ui/ui/library.slint#L1242), [`ui/dr-ui/ui/library.slint:1267`](../ui/dr-ui/ui/library.slint#L1267), [`ui/dr-ui/ui/library.slint:1274`](../ui/dr-ui/ui/library.slint#L1274), [`ui/dr-ui/ui/library.slint:1330`](../ui/dr-ui/ui/library.slint#L1330), [`ui/dr-ui/ui/library.slint:1337`](../ui/dr-ui/ui/library.slint#L1337), [`ui/dr-ui/ui/library.slint:2692`](../ui/dr-ui/ui/library.slint#L2692), [`ui/dr-ui/ui/library.slint:3828`](../ui/dr-ui/ui/library.slint#L3828), [`ui/dr-ui/ui/library.slint:850`](../ui/dr-ui/ui/library.slint#L850), [`ui/dr-ui/ui/library.slint:900`](../ui/dr-ui/ui/library.slint#L900), [`ui/dr-ui/ui/library.slint:906`](../ui/dr-ui/ui/library.slint#L906), [`ui/dr-ui/ui/library.slint:925`](../ui/dr-ui/ui/library.slint#L925) |
| FR-UI-5 | [`ui/dr-ui/src/collections_ui.rs:1`](../ui/dr-ui/src/collections_ui.rs#L1), [`ui/dr-ui/src/lib.rs:2609`](../ui/dr-ui/src/lib.rs#L2609), [`ui/dr-ui/src/lib.rs:3211`](../ui/dr-ui/src/lib.rs#L3211), [`ui/dr-ui/src/lib.rs:3396`](../ui/dr-ui/src/lib.rs#L3396), [`ui/dr-ui/src/masks_ui.rs:863`](../ui/dr-ui/src/masks_ui.rs#L863), [`ui/dr-ui/ui/app.slint:109`](../ui/dr-ui/ui/app.slint#L109), [`ui/dr-ui/ui/collections.slint:4`](../ui/dr-ui/ui/collections.slint#L4) |
| FR-UI-6 | [`ui/dr-ui/ui/widgets.slint:1`](../ui/dr-ui/ui/widgets.slint#L1) |
| FR-UI-7 | [`core/dr-pipeline/src/descriptor.rs:177`](../core/dr-pipeline/src/descriptor.rs#L177), [`core/dr-pipeline/src/framing.rs:385`](../core/dr-pipeline/src/framing.rs#L385) |
diff --git a/tools/traceability/src/gestures.rs b/tools/traceability/src/gestures.rs
index 1c797e7..3d10f00 100644
--- a/tools/traceability/src/gestures.rs
+++ b/tools/traceability/src/gestures.rs
@@ -45,8 +45,14 @@
//! Works in `.rs` and `.slint` alike, because both comment with `//` and the
//! gestures live in both — the arbitration in Rust, the affordance in Slint.
//!
-//! A block runs from the `GESTURE:` line to the first line that is not a
-//! comment, or to a comment line that is empty after its marker. A line reading
+//! **The tag opens its comment or it is not a tag.** A line that merely
+//! mentions it — a sentence about the vocabulary, a worked example in a doc
+//! comment like the one above — is describing the mechanism rather than
+//! declaring a gesture, and position is the only thing that tells the two
+//! apart.
+//!
+//! A block runs from that line to the first line that is not a comment, or to a
+//! comment line that is empty after its marker. A line reading
//! `key: value` sets a field; any other line inside the block continues the
//! field before it, which is what lets `why` run to a sentence. An unrecognised
//! key is an error rather than a silently dropped line: `pointr:` should not
@@ -127,20 +133,26 @@ pub fn extract_from_text(text: &str, path: &str) -> (Vec, Vec = BTreeMap::new();
let mut last: Option = None;
@@ -236,17 +248,6 @@ pub fn extract_from_text(text: &str, path: &str) -> (Vec, Vec bool {
- let t = line.trim_start();
- t.starts_with("//")
-}
-
/// The text of a comment line, marker stripped. `None` for anything else.
///
/// `///` and `//!` are stripped to the same thing: a gesture block written as a
@@ -525,6 +526,21 @@ property ranging: false;
assert!(p.is_empty());
}
+ /// Prose *about* the vocabulary is not a member of it. The gate caught this
+ /// exact sentence in `dr-ui`'s module list, where a comment explaining
+ /// where the generated table comes from named the tag in passing.
+ #[test]
+ fn a_sentence_that_mentions_the_tag_is_not_a_gesture() {
+ let text = "\
+// Generated from the `GESTURE:` comments beside the code that implements
+// each one — see `tools/traceability`.
+mod gesture_book;
+";
+ let (g, p) = extract_from_text(text, "lib.rs");
+ assert!(g.is_empty(), "{g:?}");
+ assert!(p.is_empty(), "{p:?}");
+ }
+
/// A blank comment line ends the block, so ordinary prose beneath a gesture
/// is not swallowed into its `why`.
#[test]