Gate the gesture vocabulary the way the matrix is gated
🐳 Android image / Build and push (push) Successful in 3s
Build and test / android-image (push) Successful in 3s
Build and test / Desktop (Linux) (push) Failing after 1h16m50s
Build and test / Layer separation (push) Successful in 39s
Traceability / Requirement traces (push) Successful in 50s
Build and test / Android (aarch64) (push) Successful in 22m27s
🐳 Android image / Build and push (push) Successful in 3s
Build and test / android-image (push) Successful in 3s
Build and test / Desktop (Linux) (push) Failing after 1h16m50s
Build and test / Layer separation (push) Successful in 39s
Traceability / Requirement traces (push) Successful in 50s
Build and test / Android (aarch64) (push) Successful in 22m27s
Three holes, all found by the gate catching itself out. **Prose that mentions the tag was read as a tag.** `dr-ui`'s module list carries a comment saying where the generated table comes from, and it names `GESTURE:` in passing; the scan extracted that sentence fragment as a gesture with no place and no way to perform it. A tag must now *open* its comment. A line that merely mentions it is describing the mechanism, not declaring a member of it, and position is the only thing that tells the two apart — which also makes the string-literal guard fall out for free rather than being a special case. **Neither artefact was regenerated on commit.** They cite line numbers, so they go stale on anything that moves a line — the sheet commit made the document wrong about every gesture in `library.slint` without touching a single one. The pre-commit hook that already keeps the matrix in step now keeps these too, and unlike the matrix it *fails* rather than shrugging when the scan does: a matrix that will not build leaves a stale one in place, where a malformed gesture block means a user about to be told the wrong thing. **CI did not check them at all.** It does now, blocking. The matrix is read; the gesture table is *shown to somebody using the application*, and a stale one tells them to perform a gesture that no longer exists — from which they will conclude the application is broken rather than the page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
+33
-6
@@ -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
|
||||
|
||||
+11
-11
@@ -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.
|
||||
|
||||
<sub>`ui/dr-ui/ui/library.slint:1259`</sub>
|
||||
<sub>`ui/dr-ui/ui/library.slint:1282`</sub>
|
||||
|
||||
### 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.
|
||||
|
||||
<sub>`ui/dr-ui/ui/library.slint:1268`</sub>
|
||||
<sub>`ui/dr-ui/ui/library.slint:1291`</sub>
|
||||
|
||||
### 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
|
||||
|
||||
<sub>`ui/dr-ui/ui/library.slint:1276`</sub>
|
||||
<sub>`ui/dr-ui/ui/library.slint:1299`</sub>
|
||||
|
||||
### 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.
|
||||
|
||||
<sub>`ui/dr-ui/ui/library.slint:1337`</sub>
|
||||
<sub>`ui/dr-ui/ui/library.slint:1360`</sub>
|
||||
|
||||
### 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.
|
||||
|
||||
<sub>`ui/dr-ui/ui/library.slint:2076`</sub>
|
||||
<sub>`ui/dr-ui/ui/library.slint:2101`</sub>
|
||||
|
||||
### 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.
|
||||
|
||||
<sub>`ui/dr-ui/ui/library.slint:2367`</sub>
|
||||
<sub>`ui/dr-ui/ui/library.slint:2392`</sub>
|
||||
|
||||
### 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.
|
||||
|
||||
<sub>`ui/dr-ui/ui/library.slint:2384`</sub>
|
||||
<sub>`ui/dr-ui/ui/library.slint:2409`</sub>
|
||||
|
||||
### 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.
|
||||
|
||||
<sub>`ui/dr-ui/ui/library.slint:2825`</sub>
|
||||
<sub>`ui/dr-ui/ui/library.slint:2850`</sub>
|
||||
|
||||
### 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.
|
||||
|
||||
<sub>`ui/dr-ui/ui/library.slint:2974`</sub>
|
||||
<sub>`ui/dr-ui/ui/library.slint:2999`</sub>
|
||||
|
||||
### 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.
|
||||
|
||||
<sub>`ui/dr-ui/ui/library.slint:3204`</sub>
|
||||
<sub>`ui/dr-ui/ui/library.slint:3229`</sub>
|
||||
|
||||
### 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.
|
||||
|
||||
<sub>`ui/dr-ui/ui/library.slint:3316`</sub>
|
||||
<sub>`ui/dr-ui/ui/library.slint:3341`</sub>
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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<Gesture>, Vec<GesturePr
|
||||
|
||||
let mut i = 0;
|
||||
while i < lines.len() {
|
||||
let Some(pos) = lines[i].find("GESTURE:") else {
|
||||
// **The tag must open its comment**, not merely appear somewhere in
|
||||
// one. Prose that mentions it — "generated from the `GESTURE:` comments
|
||||
// beside the code" — is talking *about* the vocabulary, not declaring a
|
||||
// member of it, and the two are only distinguishable by position. The
|
||||
// gate caught exactly that sentence in `dr-ui`'s own module list, which
|
||||
// is the right outcome for a typo and the wrong one for a description.
|
||||
//
|
||||
// This also excludes a tag inside a string literal, which has code in
|
||||
// front of it and so does not open a comment at all.
|
||||
let Some(body) = comment_body(lines[i]) else {
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
// Only in a comment. A `GESTURE:` inside a string literal — this very
|
||||
// file's doc comment shows the tag, and so does the scanner's own test
|
||||
// fixture — is not a tag.
|
||||
if !is_comment(lines[i]) {
|
||||
let Some(title) = body.trim_start().strip_prefix("GESTURE:") else {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let start = i;
|
||||
let title = lines[i][pos + "GESTURE:".len()..].trim().to_string();
|
||||
let title = title.trim().to_string();
|
||||
|
||||
let mut fields: BTreeMap<String, String> = BTreeMap::new();
|
||||
let mut last: Option<String> = None;
|
||||
@@ -236,17 +248,6 @@ pub fn extract_from_text(text: &str, path: &str) -> (Vec<Gesture>, Vec<GesturePr
|
||||
(out, problems)
|
||||
}
|
||||
|
||||
/// Whether a line is a `//` comment and nothing else of substance before it.
|
||||
///
|
||||
/// Deliberately strict about what precedes the marker: `let s = "// GESTURE:"`
|
||||
/// has code in front of it and is not a comment line. Trailing comments after
|
||||
/// code are not a place gesture blocks are written, so refusing them costs
|
||||
/// nothing and keeps string literals out of the document.
|
||||
fn is_comment(line: &str) -> 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 <bool> 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]
|
||||
|
||||
Reference in New Issue
Block a user