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

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:
2026-08-30 00:51:13 +02:00
co-authored by Claude Opus 5
parent 1cd5ab6815
commit 091306f736
5 changed files with 94 additions and 38 deletions
+36 -20
View File
@@ -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]