Skip to main content

max / makenotwork

Clear clippy warnings and unblock the migration hygiene gate Prep for adding clippy/fmt/audit/deny as Sando gates: -D warnings was enforced in exactly one place (server/deploy/run-ci.sh, dying with the astra pipeline), so 39 warnings had accumulated across three crates. wam and mnw-cli took cargo clippy --fix for the collapsible-if and needless-return classes. The judgement calls: - BroadcastResult.success and DomainVerifyResult.verified are the wire contract for endpoints that return them, so they get the same #[allow(dead_code)] the neighbouring response structs already carry rather than being deleted. - remove_item_tag / create_collection / delete_collection are unused by the TUI but keep the client mirroring the full /api/internal surface. - discover.rs::required_control_names was dead and could never have been otherwise: it is pub(crate) #[cfg(test)] behind two private module boundaries, and its only intended caller is an integration test in a separate crate. Deleted, and the test's doc comment corrected — it claimed the control list was derived from query_param_contract when it is hardcoded and can drift. Separately, migration 169 violated the repo's own re-run safety convention (CREATE TABLE / CREATE INDEX without IF NOT EXISTS), so migration_hygiene has been red on main since 453d9843 -- meaning Sando's cargo_test gate was already failing before any of today's changes. 169 has never been applied anywhere (the scratch DB restored from prod tops out at 168), so adding IF NOT EXISTS causes no checksum drift. Also bumps crossbeam-epoch 0.9.18 -> 0.9.20 in the four lockfiles carrying it, closing RUSTSEC-2026-0204 ahead of the cargo audit gate.
Author: Max Johnson <me@maxj.phd> · 2026-07-21 20:18 UTC
Commit: 87063f69064782ca27a291dfd044cb35024f2e1c
Parent: 2e45094
23 files changed, +76 insertions, -97 deletions
@@ -254,6 +254,7 @@ pub struct TagInfo {
254 254
255 255 /// Result of a broadcast send.
256 256 #[derive(Debug, Deserialize)]
257 + #[allow(dead_code)] // wire contract: every field the endpoint returns, read or not
257 258 pub struct BroadcastResult {
258 259 pub success: bool,
259 260 pub recipient_count: usize,
@@ -292,6 +293,7 @@ pub struct DomainInfo {
292 293
293 294 /// Domain verification result.
294 295 #[derive(Debug, Deserialize)]
296 + #[allow(dead_code)] // wire contract: every field the endpoint returns, read or not
295 297 pub struct DomainVerifyResult {
296 298 pub verified: bool,
297 299 pub message: String,
@@ -1371,6 +1373,9 @@ impl MnwApiClient {
1371 1373 empty_response(resp, "add_item_tag").await
1372 1374 }
1373 1375
1376 + // Unused by the TUI today; kept so the client mirrors the full
1377 + // /api/internal surface rather than only the paths one caller happens to hit.
1378 + #[allow(dead_code)]
1374 1379 pub async fn remove_item_tag(&self, user_id: &str, item_id: &str, tag_id: &str) -> anyhow::Result<()> {
1375 1380 let url = format!("{}/api/internal/creator/items/tags/remove", self.base_url);
1376 1381 let resp = self.http.post(&url).bearer_auth(&self.service_token)
@@ -1411,6 +1416,7 @@ impl MnwApiClient {
1411 1416 json_response(resp, "list_collections").await
1412 1417 }
1413 1418
1419 + #[allow(dead_code)]
1414 1420 pub async fn create_collection(&self, user_id: &str, slug: &str, title: &str) -> anyhow::Result<serde_json::Value> {
1415 1421 let url = format!("{}/api/internal/creator/collections", self.base_url);
1416 1422 let resp = self.http.post(&url).bearer_auth(&self.service_token)
@@ -1420,6 +1426,7 @@ impl MnwApiClient {
1420 1426 json_response(resp, "create_collection").await
1421 1427 }
1422 1428
1429 + #[allow(dead_code)]
1423 1430 pub async fn delete_collection(&self, user_id: &str, collection_id: &str) -> anyhow::Result<()> {
1424 1431 let url = format!("{}/api/internal/creator/collections/{}", self.base_url, collection_id);
1425 1432 let resp = self.http.delete(&url).bearer_auth(&self.service_token)
@@ -53,7 +53,7 @@ pub async fn execute(
53 53 _ => b"Usage: project create --title \"Name\" [--type audio|digital|video|mixed|subscription] [--description \"...\"]\r\n".to_vec(),
54 54 },
55 55 "upload" => {
56 - return b"Pipe uploads use stdin. Example:\r\n cat file.wav | ssh cli.makenot.work upload --filename track.wav --project my-project\r\n".to_vec();
56 + b"Pipe uploads use stdin. Example:\r\n cat file.wav | ssh cli.makenot.work upload --filename track.wav --project my-project\r\n".to_vec()
57 57 }
58 58 "projects" => cmd_projects(user, api, json).await,
59 59 "analytics" => {
@@ -403,11 +403,10 @@ async fn cmd_domain_show(user: &UserInfo, api: &MnwApiClient) -> Vec<u8> {
403 403 Ok(Some(d)) => {
404 404 let status = if d.verified { "verified" } else { "pending" };
405 405 let mut out = format!("Domain: {} ({})\r\n", d.domain, status);
406 - if !d.verified {
407 - if let Some(ref instr) = d.instructions {
406 + if !d.verified
407 + && let Some(ref instr) = d.instructions {
408 408 out.push_str(&format!("{}\r\n", instr));
409 409 }
410 - }
411 410 out.into_bytes()
412 411 }
413 412 Ok(None) => b"No custom domain configured.\r\nUsage: domain add <domain>\r\n".to_vec(),
@@ -557,8 +556,8 @@ pub fn help_text() -> Vec<u8> {
557 556 /// Handles quoted values that were split by whitespace by rejoining until the closing quote.
558 557 fn extract_flag(parts: &[&str], flags: &[&str]) -> Option<String> {
559 558 for (i, part) in parts.iter().enumerate() {
560 - if flags.contains(part) {
561 - if let Some(&next) = parts.get(i + 1) {
559 + if flags.contains(part)
560 + && let Some(&next) = parts.get(i + 1) {
562 561 // If the value starts with a quote, collect until closing quote
563 562 if next.starts_with('"') || next.starts_with('\'') {
564 563 let quote = next.as_bytes()[0] as char;
@@ -579,7 +578,6 @@ fn extract_flag(parts: &[&str], flags: &[&str]) -> Option<String> {
579 578 }
580 579 return Some(next.to_string());
581 580 }
582 - }
583 581 }
584 582 None
585 583 }
@@ -86,15 +86,14 @@ impl russh::server::Handler for MnwHandler {
86 86 key: &PublicKey,
87 87 ) -> Result<Auth, Self::Error> {
88 88 // Per-IP rate limiting: reject early if threshold exceeded
89 - if let Some(addr) = self.peer_addr {
90 - if !self.rate_limiter.check(addr.ip()) {
89 + if let Some(addr) = self.peer_addr
90 + && !self.rate_limiter.check(addr.ip()) {
91 91 tracing::warn!(peer = %addr, "auth rate limit exceeded");
92 92 return Ok(Auth::Reject {
93 93 proceed_with_methods: None,
94 94 partial_success: false,
95 95 });
96 96 }
97 - }
98 97
99 98 let fingerprint = key.fingerprint(HashAlg::Sha256).to_string();
100 99 tracing::debug!(%fingerprint, peer = ?self.peer_addr, "key offered");
@@ -424,10 +423,10 @@ impl russh::server::Handler for MnwHandler {
424 423 let mut i = 1;
425 424 while i < parts.len() {
426 425 match parts[i] {
427 - "--filename" | "-f" => { if i + 1 < parts.len() { filename = parts[i + 1].to_string(); i += 1; } }
428 - "--project" | "-p" => { if i + 1 < parts.len() { project_slug = parts[i + 1].to_string(); i += 1; } }
429 - "--title" | "-t" => { if i + 1 < parts.len() { title = parts[i + 1].to_string(); i += 1; } }
430 - "--price" => { if i + 1 < parts.len() { price_cents = parts[i + 1].parse().unwrap_or(0); i += 1; } }
426 + "--filename" | "-f" if i + 1 < parts.len() => { filename = parts[i + 1].to_string(); i += 1; }
427 + "--project" | "-p" if i + 1 < parts.len() => { project_slug = parts[i + 1].to_string(); i += 1; }
428 + "--title" | "-t" if i + 1 < parts.len() => { title = parts[i + 1].to_string(); i += 1; }
429 + "--price" if i + 1 < parts.len() => { price_cents = parts[i + 1].parse().unwrap_or(0); i += 1; }
431 430 _ => {}
432 431 }
433 432 i += 1;
@@ -60,7 +60,7 @@ pub async fn list_staged_files(dir: &Path) -> Vec<StagedFile> {
60 60 });
61 61 }
62 62
63 - files.sort_by(|a, b| b.modified.cmp(&a.modified));
63 + files.sort_by_key(|f| std::cmp::Reverse(f.modified));
64 64 files
65 65 }
66 66
@@ -10,16 +10,14 @@ pub(crate) async fn handle_analytics_input(
10 10 tx: &mpsc::Sender<AppEvent>,
11 11 ) {
12 12 match key.code {
13 - KeyCode::Char('j') | KeyCode::Down => {
14 - if app.analytics_show_transactions {
13 + KeyCode::Char('j') | KeyCode::Down
14 + if app.analytics_show_transactions => {
15 15 app.move_down(screen);
16 16 }
17 - }
18 - KeyCode::Char('k') | KeyCode::Up => {
19 - if app.analytics_show_transactions {
17 + KeyCode::Char('k') | KeyCode::Up
18 + if app.analytics_show_transactions => {
20 19 app.move_up(screen);
21 20 }
22 - }
23 21 KeyCode::Esc => {
24 22 if app.analytics_show_transactions {
25 23 app.analytics_show_transactions = false;
@@ -138,8 +138,8 @@ pub(crate) async fn handle_blog_input(
138 138 app.edit_buffer.clear();
139 139 app.blog_status = Some("Title: _".to_string());
140 140 }
141 - KeyCode::Char('d') | KeyCode::Char('D') => {
142 - if !app.blog_posts.is_empty() {
141 + KeyCode::Char('d') | KeyCode::Char('D')
142 + if !app.blog_posts.is_empty() => {
143 143 let idx = app.selected_index;
144 144 if matches!(app.confirm_action, Some(ConfirmAction::DeleteBlogPost { post_idx }) if post_idx == idx) {
145 145 // Confirmed — execute delete
@@ -183,7 +183,6 @@ pub(crate) async fn handle_blog_input(
183 183 ));
184 184 }
185 185 }
186 - }
187 186 KeyCode::Char('r') | KeyCode::Char('R') => {
188 187 if let Screen::Blog(_, project_id) = &*screen {
189 188 app.loading = true;
@@ -13,8 +13,8 @@ pub(crate) async fn handle_home_input(
13 13 match key.code {
14 14 KeyCode::Char('j') | KeyCode::Down => app.move_down(screen),
15 15 KeyCode::Char('k') | KeyCode::Up => app.move_up(screen),
16 - KeyCode::Enter => {
17 - if !app.projects.is_empty() {
16 + KeyCode::Enter
17 + if !app.projects.is_empty() => {
18 18 let idx = app.selected_index;
19 19 let project_id = app.projects[idx].id.clone();
20 20 let user_id = app.user.user_id.clone();
@@ -29,7 +29,6 @@ pub(crate) async fn handle_home_input(
29 29 load_project_items(&api, &project_id, &user_id, &tx).await;
30 30 });
31 31 }
32 - }
33 32 KeyCode::Char('u') | KeyCode::Char('U') => {
34 33 *screen = Screen::Upload;
35 34 app.selected_index = 0;
@@ -365,14 +365,13 @@ pub(crate) async fn handle_item_input(
365 365 }
366 366 }
367 367 }
368 - KeyCode::Char('t') | KeyCode::Char('T') => {
369 - if app.item_detail.is_some() {
368 + KeyCode::Char('t') | KeyCode::Char('T')
369 + if app.item_detail.is_some() => {
370 370 app.tag_searching = true;
371 371 app.edit_buffer.clear();
372 372 app.tag_search_results.clear();
373 373 app.item_status = Some("Tag: type to search, Enter to add first result, Esc to cancel".to_string());
374 374 }
375 - }
376 375 KeyCode::Char('l') | KeyCode::Char('L') => {
377 376 // Open license keys screen
378 377 if let Screen::Item(project_idx, item_id) = &*screen {
@@ -65,8 +65,8 @@ pub(crate) async fn handle_keys_input(
65 65 });
66 66 }
67 67 }
68 - KeyCode::Char('x') | KeyCode::Char('X') => {
69 - if !app.license_keys.is_empty() {
68 + KeyCode::Char('x') | KeyCode::Char('X')
69 + if !app.license_keys.is_empty() => {
70 70 let idx = app.selected_index;
71 71 if matches!(app.confirm_action, Some(ConfirmAction::RevokeLicenseKey { key_idx }) if key_idx == idx) {
72 72 // Confirmed — execute revoke
@@ -110,7 +110,6 @@ pub(crate) async fn handle_keys_input(
110 110 ));
111 111 }
112 112 }
113 - }
114 113 KeyCode::Char('r') | KeyCode::Char('R') => {
115 114 if let Screen::Keys(_, item_id) = &*screen {
116 115 app.loading = true;
@@ -44,9 +44,9 @@ pub(crate) async fn handle_project_input(
44 44 match key.code {
45 45 KeyCode::Char('j') | KeyCode::Down => app.move_down(screen),
46 46 KeyCode::Char('k') | KeyCode::Up => app.move_up(screen),
47 - KeyCode::Char(' ') => {
47 + KeyCode::Char(' ')
48 48 // Toggle selection
49 - if !app.items.is_empty() {
49 + if !app.items.is_empty() => {
50 50 let idx = app.selected_index;
51 51 if app.selected_items.contains(&idx) {
52 52 app.selected_items.remove(&idx);
@@ -54,7 +54,6 @@ pub(crate) async fn handle_project_input(
54 54 app.selected_items.insert(idx);
55 55 }
56 56 }
57 - }
58 57 KeyCode::Esc => {
59 58 if !app.selected_items.is_empty() {
60 59 app.selected_items.clear();
@@ -87,8 +86,8 @@ pub(crate) async fn handle_project_input(
87 86 });
88 87 }
89 88 }
90 - KeyCode::Char('p') | KeyCode::Char('P') => {
91 - if !app.items.is_empty() {
89 + KeyCode::Char('p') | KeyCode::Char('P')
90 + if !app.items.is_empty() => {
92 91 if app.selected_items.is_empty() {
93 92 // Single item: toggle publish state
94 93 let item = &app.items[app.selected_index];
@@ -122,9 +121,8 @@ pub(crate) async fn handle_project_input(
122 121 }
123 122 }
124 123 }
125 - }
126 - KeyCode::Char('d') | KeyCode::Char('D') => {
127 - if !app.items.is_empty() {
124 + KeyCode::Char('d') | KeyCode::Char('D')
125 + if !app.items.is_empty() => {
128 126 if app.selected_items.is_empty() {
129 127 // Select current item for single delete
130 128 app.selected_items.insert(app.selected_index);
@@ -132,7 +130,6 @@ pub(crate) async fn handle_project_input(
132 130 let count = app.selected_items.len();
133 131 app.confirm_action = Some(ConfirmAction::BulkDelete { count });
134 132 }
135 - }
136 133 KeyCode::Char('b') | KeyCode::Char('B') => {
137 134 if let Screen::Project(idx) = screen
138 135 && let Some(p) = app.projects.get(*idx)
@@ -100,8 +100,8 @@ pub(crate) async fn handle_promo_input(
100 100 app.edit_buffer.clear();
101 101 app.promo_status = Some("Code: _".to_string());
102 102 }
103 - KeyCode::Char('d') | KeyCode::Char('D') => {
104 - if !app.promo_codes.is_empty() {
103 + KeyCode::Char('d') | KeyCode::Char('D')
104 + if !app.promo_codes.is_empty() => {
105 105 let idx = app.selected_index;
106 106 if matches!(app.confirm_action, Some(ConfirmAction::DeletePromoCode { code_idx }) if code_idx == idx) {
107 107 // Confirmed — execute delete
@@ -141,7 +141,6 @@ pub(crate) async fn handle_promo_input(
141 141 ));
142 142 }
143 143 }
144 - }
145 144 KeyCode::Char('r') | KeyCode::Char('R') => {
146 145 app.loading = true;
147 146 let api = api.clone();
@@ -102,11 +102,10 @@ pub(crate) async fn handle_upload_input(
102 102 }
103 103
104 104 // Cancel pending delete confirmation on non-d keys
105 - if !matches!(key.code, KeyCode::Char('d') | KeyCode::Char('D')) {
106 - if app.upload_status.as_ref().is_some_and(|s| s.starts_with("Delete '") && s.ends_with("'? Press d again")) {
105 + if !matches!(key.code, KeyCode::Char('d') | KeyCode::Char('D'))
106 + && app.upload_status.as_ref().is_some_and(|s| s.starts_with("Delete '") && s.ends_with("'? Press d again")) {
107 107 app.upload_status = None;
108 108 }
109 - }
110 109
111 110 // Normal mode
112 111 match key.code {
@@ -119,8 +118,8 @@ pub(crate) async fn handle_upload_input(
119 118 app.upload_status = None;
120 119 app.selected_index = 0;
121 120 }
122 - KeyCode::Char('e') | KeyCode::Char('E') => {
123 - if !app.staged_files.is_empty() {
121 + KeyCode::Char('e') | KeyCode::Char('E')
122 + if !app.staged_files.is_empty() => {
124 123 let idx = app.selected_index;
125 124 let current_title = app.file_metadata.get(idx).and_then(|m| m.title.clone());
126 125 app.editing_field = Some(EditField::Title);
@@ -128,9 +127,8 @@ pub(crate) async fn handle_upload_input(
128 127 app.upload_status =
129 128 Some("Title: _ (Enter to save, Tab for next field, Esc to cancel)".to_string());
130 129 }
131 - }
132 - KeyCode::Char('p') | KeyCode::Char('P') => {
133 - if !app.staged_files.is_empty() && !app.publishing {
130 + KeyCode::Char('p') | KeyCode::Char('P')
131 + if !app.staged_files.is_empty() && !app.publishing => {
134 132 let idx = app.selected_index;
135 133 let file = &app.staged_files[idx];
136 134 let meta = app.file_metadata.get(idx).cloned().unwrap_or_default();
@@ -198,9 +196,8 @@ pub(crate) async fn handle_upload_input(
198 196 .await;
199 197 });
200 198 }
201 - }
202 - KeyCode::Char('d') | KeyCode::Char('D') => {
203 - if !app.staged_files.is_empty() {
199 + KeyCode::Char('d') | KeyCode::Char('D')
200 + if !app.staged_files.is_empty() => {
204 201 let idx = app.selected_index;
205 202 let filename = &app.staged_files[idx].filename;
206 203 if app.upload_status.as_ref().is_some_and(|s| s.starts_with("Delete '") && s.ends_with("'? Press d again")) {
@@ -223,7 +220,6 @@ pub(crate) async fn handle_upload_input(
223 220 app.upload_status = Some(format!("Delete '{}'? Press d again", filename));
224 221 }
225 222 }
226 - }
227 223 KeyCode::Char('r') | KeyCode::Char('R') => {
228 224 app.loading = true;
229 225 let staging_dir = staging_dir.to_path_buf();
@@ -166,7 +166,9 @@ enum Screen {
166 166 /// Collections management.
167 167 Collections,
168 168 /// Subscription tiers for a project. Stores (project_index, project_id).
169 - Tiers(usize, String),
169 + /// The id is carried for the tier-mutation calls the screen will need; the
170 + /// render path only uses the index today.
171 + Tiers(usize, #[allow(dead_code)] String),
170 172 }
171 173
172 174 /// User-editable metadata for a staged file.
@@ -505,7 +507,7 @@ mod tests {
505 507
506 508 #[test]
507 509 fn parse_key_printable_char() {
508 - let key = parse_key(&[b'a']).unwrap();
510 + let key = parse_key(b"a").unwrap();
509 511 assert_eq!(key.code, KeyCode::Char('a'));
510 512 }
511 513
@@ -309,8 +309,8 @@ pub fn launch(
309 309 app.item_status = Some(message);
310 310 app.selected_items.clear();
311 311 // Reload project items
312 - if let Screen::Project(pidx) = &screen {
313 - if let Some(p) = app.projects.get(*pidx) {
312 + if let Screen::Project(pidx) = &screen
313 + && let Some(p) = app.projects.get(*pidx) {
314 314 app.loading = true;
315 315 let api = api.clone();
316 316 let project_id = p.id.clone();
@@ -320,7 +320,6 @@ pub fn launch(
320 320 load_project_items(&api, &project_id, &user_id, &tx).await;
321 321 });
322 322 }
323 - }
324 323 }
325 324 DataPayload::ProjectReload { project_idx } => {
326 325 if let Some(p) = app.projects.get(project_idx) {
M pom/Cargo.lock +2 -2
@@ -505,9 +505,9 @@ dependencies = [
505 505
506 506 [[package]]
507 507 name = "crossbeam-epoch"
508 - version = "0.9.18"
508 + version = "0.9.20"
509 509 source = "registry+https://github.com/rust-lang/crates.io-index"
510 - checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
510 + checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
511 511 dependencies = [
512 512 "crossbeam-utils",
513 513 ]
@@ -368,9 +368,9 @@ checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
368 368
369 369 [[package]]
370 370 name = "crossbeam-epoch"
371 - version = "0.9.18"
371 + version = "0.9.20"
372 372 source = "registry+https://github.com/rust-lang/crates.io-index"
373 - checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
373 + checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
374 374 dependencies = [
375 375 "crossbeam-utils",
376 376 ]
@@ -2128,9 +2128,9 @@ dependencies = [
2128 2128
2129 2129 [[package]]
2130 2130 name = "crossbeam-epoch"
2131 - version = "0.9.18"
2131 + version = "0.9.20"
2132 2132 source = "registry+https://github.com/rust-lang/crates.io-index"
2133 - checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
2133 + checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
2134 2134 dependencies = [
2135 2135 "crossbeam-utils",
2136 2136 ]
@@ -16,7 +16,7 @@
16 16 -- `dedup_key` lets an agent collapse repeated firings of the same condition
17 17 -- (e.g. one row per (source, dedup_key) flap) without the server modeling agent
18 18 -- state; it is nullable for one-shot alerts.
19 - CREATE TABLE admin_alerts (
19 + CREATE TABLE IF NOT EXISTS admin_alerts (
20 20 id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
21 21 source TEXT NOT NULL,
22 22 kind TEXT NOT NULL,
@@ -30,4 +30,4 @@ CREATE TABLE admin_alerts (
30 30 );
31 31
32 32 -- The admin log view is "newest first", so index the sort key.
33 - CREATE INDEX idx_admin_alerts_received ON admin_alerts (received_at DESC);
33 + CREATE INDEX IF NOT EXISTS idx_admin_alerts_received ON admin_alerts (received_at DESC);
@@ -786,17 +786,6 @@ fn query_param_contract() -> Vec<(&'static str, bool)> {
786 786 ]
787 787 }
788 788
789 - /// The names the rendered page must expose as form controls, for the
790 - /// integration test that checks the markup.
791 - #[cfg(test)]
792 - pub(crate) fn required_control_names() -> Vec<&'static str> {
793 - query_param_contract()
794 - .into_iter()
795 - .filter(|(_, required)| *required)
796 - .map(|(name, _)| name)
797 - .collect()
798 - }
799 -
800 789 #[cfg(test)]
801 790 mod query_contract_tests {
802 791 use super::*;
@@ -723,10 +723,12 @@ async fn sidebar_uses_only_defined_style_hooks() {
723 723 ///
724 724 /// A form's control names are an untested contract with its handler. A rename
725 725 /// pass once rewrote `name="has_source"` to `name="sidebar.has_source"` and the
726 - /// whole suite passed with the filter inert. The list is derived from an
727 - /// exhaustive destructure of `DiscoverQuery` (see `query_param_contract`), so a
728 - /// new query parameter cannot be added without deciding whether it needs a
729 - /// control here.
726 + /// whole suite passed with the filter inert.
727 + ///
728 + /// The list below is hardcoded. `query_param_contract` in the handler is the
729 + /// exhaustive destructure of `DiscoverQuery`, but it lives behind two private
730 + /// module boundaries and this is a separate crate, so the two can drift: adding
731 + /// a query parameter there does NOT fail anything here.
730 732 #[tokio::test]
731 733 async fn every_filter_param_has_a_control_in_the_markup() {
732 734 let mut h = TestHarness::new().await;
M wam/src/main.rs +1 -1
M wam/src/tui.rs +6 -9