Skip to main content

max / audiofiles

Describe the tag review queue Five shapes: the screen, the head, the tag list, the open tag and the foot. The open tag was a let-else and eight conditionals over a group that may not exist; read into an Option first, it is one loop and the guards say what the shipped screen's own gates said. The window over the group stays a described fact rather than a renderer's: `more` carries what was never fetched, and the buttons still act on all of it. The route that opens a tag is `open_tag` now. `read` is what a module in this transition calls the function in front of its declarations, and the handler had the name first.
Author: Max Johnson <me@maxj.phd> · 2026-09-04 21:12 UTC
Signed with PGP, not checked
Commit: 1ec00213ca84c7d299658cc9ac0fc19c991b2cbc
Parent: 6970b59
1 file changed, +236 insertions, -167 deletions
@@ -63,11 +63,9 @@
63 63 //! [`Candidate::confident`](super::Candidate::confident) can do exactly that;
64 64 //! choosing the colour is not the description's.
65 65
66 + use quasi_declare::declare;
66 67 use quasi_router::layout::Tone;
67 - use quasi_router::{
68 - Act, Action, Figure, Node, Prose, RegionKind, Request, Response, Rest, RouteError, Router, Row,
69 - Screen, Slot,
70 - };
68 + use quasi_router::{Action, Request, Response, Rest, RouteError, Router};
71 69
72 70 use super::{Group, Panels, Queued, Scope};
73 71
@@ -107,7 +105,7 @@
107 105 .post("/review/dismiss", dismiss)
108 106 .post("/review/rescan", rescan)
109 107 .post("/review/close", close)
110 - .post("/review/{at}/read", read)
108 + .post("/review/{at}/read", open_tag)
111 109 }
112 110
113 111 /// `GET /review`
@@ -115,15 +113,15 @@
115 113 /// A refusal when there is nothing queued, which is the shipped screen's own
116 114 /// exit said the only way a route can say it. See the module header.
117 115 fn index(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> {
118 - Ok(screen(&queued(state)?).into())
116 + Ok(showing(&queued(state)?))
119 117 }
120 118
121 119 /// `POST /review/{at}/read`
122 - fn read(state: &Panels<'_>, request: Request) -> Result<Response, RouteError> {
120 + fn open_tag(state: &Panels<'_>, request: Request) -> Result<Response, RouteError> {
123 121 let queued = queued(state)?;
124 122 let at = group_at(&queued, &request)?;
125 123 state.queue.read(at);
126 - Ok(screen(&queued).into())
124 + Ok(showing(&queued))
127 125 }
128 126
129 127 /// `POST /review/rows/{at}/tick`
@@ -142,21 +140,21 @@
142 140 return Err(RouteError::not_found("no such candidate"));
143 141 }
144 142 state.queue.tick(at);
145 - Ok(screen(&queued).into())
143 + Ok(showing(&queued))
146 144 }
147 145
148 146 /// `POST /review/rows/check`
149 147 fn check_shown(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> {
150 148 let queued = queued(state)?;
151 149 state.queue.tick_shown(true);
152 - Ok(screen(&queued).into())
150 + Ok(showing(&queued))
153 151 }
154 152
155 153 /// `POST /review/rows/uncheck`
156 154 fn uncheck_shown(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> {
157 155 let queued = queued(state)?;
158 156 state.queue.tick_shown(false);
159 - Ok(screen(&queued).into())
157 + Ok(showing(&queued))
160 158 }
161 159
162 160 /// `POST /review/accept/{scope}`
@@ -180,7 +178,7 @@
180 178 }
181 179
182 180 state.queue.accept(scope);
183 - Ok(screen(&queued).into())
181 + Ok(showing(&queued))
184 182 }
185 183
186 184 /// `POST /review/accept-confident`
@@ -190,7 +188,7 @@
190 188 return Err(RouteError::not_found("nothing clears its threshold"));
191 189 }
192 190 state.queue.accept_confident();
193 - Ok(screen(&queued).into())
191 + Ok(showing(&queued))
194 192 }
195 193
196 194 /// `POST /review/dismiss`
@@ -198,7 +196,7 @@
198 196 let queued = queued(state)?;
199 197 open_group(&queued)?;
200 198 state.queue.dismiss();
201 - Ok(screen(&queued).into())
199 + Ok(showing(&queued))
202 200 }
203 201
204 202 /// `POST /review/rescan`
@@ -212,14 +210,14 @@
212 210 return Err(RouteError::not_found("a pass is already running"));
213 211 }
214 212 state.queue.rescan();
215 - Ok(screen(&queued).into())
213 + Ok(showing(&queued))
216 214 }
217 215
218 216 /// `POST /review/close`
219 217 fn close(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> {
220 218 let queued = queued(state)?;
221 219 state.queue.close();
222 - Ok(screen(&queued).into())
220 + Ok(showing(&queued))
223 221 }
224 222
225 223 /// The queue, refusing the screen when there is nothing in it.
@@ -251,154 +249,155 @@
251 249 .ok_or_else(|| RouteError::not_found("no tag is open"))
252 250 }
253 251
254 - /// The screen: what the pass found, the tags, and the open one.
255 - fn screen(queued: &Queued) -> Screen {
256 - Screen::sidebar_content("Review Tags")
257 - .with(head(queued))
258 - .with(
259 - Slot::new("review-split", RegionKind::Split)
260 - .with(Node::Region(tags(queued)))
261 - .with(Node::Region(group(queued))),
262 - )
263 - .with(foot(queued))
252 + /// The screen, read and then described.
253 + pub(super) fn showing(queued: &Queued) -> Response {
254 + Response::from(screen(&read(queued)))
264 255 }
265 256
266 - /// What the pass found, and the one queue-wide gesture.
267 - fn head(queued: &Queued) -> Slot {
268 - let mut band = Slot::new(HEAD, RegionKind::Band)
269 - .with(Node::page("Review Tags"))
270 - .with(Node::text(format!(
257 + /// What the screen draws, read off the queue.
258 + struct Review {
259 + /// What the pass found, in one line.
260 + found: String,
261 + /// The queue-wide accept, while there is anything in it.
262 + confident: Option<Accept>,
263 + /// Every tag with something waiting under it.
264 + groups: Vec<Listed>,
265 + /// The open tag, if one is open.
266 + open: Option<Open>,
267 + /// Whether a pass is running.
268 + rescanning: bool,
269 + /// What the last accept did, if it has said anything.
270 + said: Option<String>,
271 + /// How many suggestions across the whole queue clear their threshold.
272 + confidently: String,
273 + }
274 +
275 + /// The closest thing left to the auto-apply this layer stopped doing, and still
276 + /// a decision made with the count on screen.
277 + struct Accept {
278 + /// What the button reads.
279 + label: String,
280 + /// What it says before it runs.
281 + confirm: String,
282 + }
283 +
284 + /// One tag in the list this screen navigates by.
285 + struct Listed {
286 + /// The tag itself.
287 + tag: String,
288 + /// How much is waiting under it, on the line beneath.
289 + waiting: String,
290 + /// Where it sits, which is what the address carries.
291 + at: usize,
292 + /// Whether it is the one open.
293 + current: bool,
294 + }
295 +
296 + /// The open tag: what it would do, and to what.
297 + struct Open {
298 + /// The tag itself.
299 + tag: String,
300 + /// How many samples would get it.
301 + said: String,
302 + /// The accept over the whole group.
303 + all: String,
304 + /// The accept over what clears the threshold, while that is a real subset.
305 + ///
306 + /// Otherwise it is a second button that does what the first one does, which
307 + /// is the shipped screen's own gate.
308 + confident: Option<String>,
309 + /// The accept over what is ticked, while anything is.
310 + checked: Option<String>,
311 + /// What dismissing the tag says before it runs.
312 + dismiss: String,
313 + /// Whether anything is ticked, which is what offers the unticking.
314 + ticked: bool,
315 + /// The candidates whose names have been resolved.
316 + shown: Vec<Waiting>,
317 + /// Whether none of them are.
318 + bare: bool,
319 + /// How many candidates there are in all.
320 + ///
321 + /// See the module header: a window over rows that were never fetched, with
322 + /// no way to widen it because the buttons act on the whole group.
323 + candidates: usize,
324 + /// How many of them are drawn.
325 + drawn: usize,
326 + /// Whether the second number is smaller than the first.
327 + windowed: bool,
328 + }
329 +
330 + /// One candidate under the open tag.
331 + struct Waiting {
332 + /// What the sample is called, or its hash until the name is resolved.
333 + name: String,
334 + /// The score, and whether it clears the threshold.
335 + ///
336 + /// The band as a fact rather than as a contrast level. A renderer is free to
337 + /// draw it the way the shipped row does.
338 + meta: String,
339 + /// Whether it is ticked.
340 + accepted: bool,
341 + /// Where it sits, which is what the tick carries.
342 + at: usize,
343 + }
344 +
345 + /// What the screen draws, read off the queue.
346 + fn read(queued: &Queued) -> Review {
347 + let total = total_of(queued);
348 + Review {
349 + found: format!(
271 350 "{} suggestion{} across {} tag{} \u{b7} {} of {} sample{}",
272 - total_of(queued),
273 - plural(total_of(queued)),
351 + total,
352 + plural(total),
274 353 queued.groups.len(),
275 354 plural(queued.groups.len()),
276 355 queued.suggested,
277 356 queued.considered,
278 357 plural(queued.considered),
279 - )))
280 - // The promise the whole screen rests on, and it is a fact about the
281 - // queue rather than about any control, so it is prose.
282 - .with(Node::text("Nothing is applied until you accept it."));
283 -
284 - // The closest thing left to the auto-apply this layer stopped doing, and
285 - // still a decision made with the count on screen. Offered only when there is
286 - // something in it, which is the shipped button's own gate.
287 - if queued.confident > 0 {
288 - band = band.with(Node::Act(
289 - Act::new(
290 - format!("Accept {} confident", queued.confident),
291 - Action::post("/review/accept-confident"),
292 - )
293 - .confirm(format!(
358 + ),
359 + confident: (queued.confident > 0).then(|| Accept {
360 + label: format!("Accept {} confident", queued.confident),
361 + confirm: format!(
294 362 "{} suggestions above their tags' thresholds will be applied across every tag. \
295 363 Accept?",
296 364 queued.confident
297 - )),
298 - ));
299 - }
300 -
301 - band
302 - }
303 -
304 - /// The tags, which is what this screen navigates by.
305 - fn tags(queued: &Queued) -> Slot {
306 - Slot::new(TAGS, RegionKind::Pane)
307 - .with(Node::section("Tags"))
308 - .with(Node::list(queued.groups.iter().enumerate().map(
309 - |(at, group)| {
310 - let mut row = Row::new(group.tag.clone())
311 - .secondary(Prose::Text(format!(
312 - "{} suggestion{}{}",
313 - group.candidates,
314 - plural(group.candidates),
315 - if group.confident > 0 {
316 - format!(", {} confident", group.confident)
317 - } else {
318 - String::new()
319 - }
320 - )))
321 - .activate(Action::post(format!("/review/{at}/read")));
322 - if at == queued.at {
323 - row.current = true;
324 - }
325 - row
326 - },
327 - )))
328 - }
329 -
330 - /// The open tag: what it would do, and to what.
331 - fn group(queued: &Queued) -> Slot {
332 - let pane = Slot::new(GROUP, RegionKind::Pane);
333 - let Some(open) = queued.groups.get(queued.at) else {
334 - return pane.with(Node::empty("Choose a tag to review."));
335 - };
336 -
337 - let mut pane = pane
338 - .with(Node::section(open.tag.clone()))
339 - .with(Node::text(format!(
340 - "{} sample{} would get this tag.",
341 - open.candidates,
342 - plural(open.candidates)
343 - )))
344 - .with(Node::Act(Act::new(
345 - format!("Accept all {}", open.candidates),
346 - Action::post("/review/accept/all"),
347 - )));
348 -
349 - // Only when it is a real subset. Otherwise it is a second button that does
350 - // what the first one does, which is the shipped screen's own gate.
351 - if open.confident > 0 && open.confident < open.candidates {
352 - pane = pane.with(Node::Act(Act::new(
353 - format!("Accept {} confident", open.confident),
354 - Action::post("/review/accept/confident"),
355 - )));
356 - }
357 - if open.checked > 0 {
358 - pane = pane.with(Node::Act(Act::new(
359 - format!("Accept {} checked", open.checked),
360 - Action::post("/review/accept/checked"),
361 - )));
362 - }
363 -
364 - pane = pane
365 - .with(Node::Act(
366 - Act::new("Dismiss tag", Action::post("/review/dismiss"))
367 - .tone(Tone::Danger)
368 - .confirm(format!(
369 - "\"{}\" and its {} suggestions will be dropped from the queue. Dismiss?",
370 - open.tag, open.candidates
371 - )),
372 - ))
373 - // Ticking is bounded to what is drawn, and that is deliberate rather
374 - // than incidental: ticking 44,000 invisible boxes would make "Accept
375 - // checked" silently mean "accept everything", which is the distinction
376 - // the three scopes exist to keep.
377 - .with(Node::Act(Act::new(
378 - "Check all shown",
379 - Action::post("/review/rows/check"),
380 - )));
381 - if open.checked > 0 {
382 - pane = pane.with(Node::Act(Act::new(
383 - "Uncheck all shown",
384 - Action::post("/review/rows/uncheck"),
385 - )));
386 - }
387 -
388 - if queued.shown.is_empty() {
389 - return pane.with(Node::empty("Nothing is waiting under this tag."));
390 - }
391 -
392 - pane.with(Node::List {
393 - rows: queued
394 - .shown
365 + ),
366 + }),
367 + groups: queued
368 + .groups
395 369 .iter()
396 370 .enumerate()
397 - .map(|(at, candidate)| {
398 - Row::new(candidate.name.clone())
399 - // The band as a fact rather than as a contrast level. A
400 - // renderer is free to draw it the way the shipped row does.
401 - .meta(format!(
371 + .map(|(at, group)| Listed {
372 + tag: group.tag.clone(),
373 + waiting: waiting(group),
374 + at,
375 + current: at == queued.at,
376 + })
377 + .collect(),
378 + open: queued.groups.get(queued.at).map(|open| Open {
379 + tag: open.tag.clone(),
380 + said: format!(
381 + "{} sample{} would get this tag.",
382 + open.candidates,
383 + plural(open.candidates)
384 + ),
385 + all: format!("Accept all {}", open.candidates),
386 + confident: (open.confident > 0 && open.confident < open.candidates)
387 + .then(|| format!("Accept {} confident", open.confident)),
388 + checked: (open.checked > 0).then(|| format!("Accept {} checked", open.checked)),
389 + dismiss: format!(
390 + "\"{}\" and its {} suggestions will be dropped from the queue. Dismiss?",
391 + open.tag, open.candidates
392 + ),
393 + ticked: open.checked > 0,
394 + shown: queued
395 + .shown
396 + .iter()
397 + .enumerate()
398 + .map(|(at, candidate)| Waiting {
399 + name: candidate.name.clone(),
400 + meta: format!(
402 401 "{:.0}%{}",
403 402 candidate.score * 100.0,
404 403 if candidate.confident {
@@ -406,43 +405,155 @@
406 405 } else {
407 406 ""
408 407 }
409 - ))
410 - .toggling(
411 - candidate.accepted,
412 - Action::post(format!("/review/rows/{at}/tick")),
413 - )
414 - })
415 - .collect(),
416 - // See the module header: a window over rows that were never fetched,
417 - // with no way to widen it because the buttons act on the whole group.
418 - more: (open.candidates > queued.shown.len())
419 - .then(|| Rest::page(0, queued.shown.len()).of(open.candidates)),
420 - })
408 + ),
409 + accepted: candidate.accepted,
410 + at,
411 + })
412 + .collect(),
413 + bare: queued.shown.is_empty(),
414 + candidates: open.candidates,
415 + drawn: queued.shown.len(),
416 + windowed: open.candidates > queued.shown.len(),
417 + }),
418 + rescanning: queued.rescanning,
419 + said: queued.said.clone(),
420 + confidently: queued.confident.to_string(),
421 + }
421 422 }
422 423
423 - /// The way out, and the way to start again.
424 - fn foot(queued: &Queued) -> Slot {
425 - let mut band = Slot::new(FOOT, RegionKind::Band)
426 - .with(Node::Act(Act::new("Close", Action::post("/review/close"))));
424 + /// How much is waiting under one tag, on the line under its name.
425 + fn waiting(group: &Group) -> String {
426 + format!(
427 + "{} suggestion{}{}",
428 + group.candidates,
429 + plural(group.candidates),
430 + if group.confident > 0 {
431 + format!(", {} confident", group.confident)
432 + } else {
433 + String::new()
434 + }
435 + )
436 + }
437 +
438 + declare! {
439 + /// The screen: what the pass found, the tags, and the open one.
440 + shape screen(review: &Review) -> Screen;
441 +
442 + screen sidebar_content "Review Tags" {
443 + include head(review);
444 + region "review-split" as Split {
445 + include tags(review);
446 + include group(review);
447 + }
448 + include foot(review);
449 + }
450 + }
451 +
452 + declare! {
453 + /// What the pass found, and the one queue-wide gesture.
454 + shape head(review: &Review) -> Slot;
455 +
456 + region HEAD as Band {
457 + page "Review Tags";
458 + text &review.found;
459 +
460 + // The promise the whole screen rests on, and it is a fact about the
461 + // queue rather than about any control, so it is prose.
462 + text "Nothing is applied until you accept it.";
463 +
464 + // The closest thing left to the auto-apply this layer stopped doing,
465 + // and still a decision made with the count on screen. Offered only when
466 + // there is something in it, which is the shipped button's own gate.
467 + for accept in review.confident.iter() {
468 + act &accept.label to post "/review/accept-confident" {
469 + confirm &accept.confirm;
470 + }
471 + }
472 + }
473 + }
474 +
475 + declare! {
476 + /// The tags, which is what this screen navigates by.
477 + shape tags(review: &Review) -> Slot;
478 +
479 + region TAGS as Pane {
480 + section "Tags";
481 + list {
482 + for listed in review.groups.iter() {
483 + row &listed.tag {
484 + secondary &listed.waiting;
485 + current listed.current;
486 + activate to post "/review/{listed.at}/read";
487 + }
488 + }
489 + }
490 + }
491 + }
492 +
493 + declare! {
494 + /// The open tag: what it would do, and to what.
495 + shape group(review: &Review) -> Slot;
Lines truncated