Skip to main content

max / audiofiles

Describe the toolbar, and close audiofiles The last workable shape in the tree, blocked since wave 15 on whether `Run` joins the shaped set. It does not need to. Two `beside` inconsistencies were what made the toolbar look unsayable, and with those repaired the whole bar is one 122-line declaration. One run and not four, which is the thing a split into sub-regions would have lost: `Shed` ranks across the whole row, so what makes room for the search box is a breadcrumb four segments away. The four `Run` suppliers are gone. `here` was four arms over `Where` and is now one shape the four places share: a folder is a trail you walk out of and the three modes are places you leave, so a mode's label is a step with nowhere to go back to and only a folder has a root above it. `leaving` was the two members every mode shares and is the read building one of those steps. audiofiles is closed: `declared 138, remaining 5`, and all five are reads answering a vocabulary value type.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01MptwXZ8k65v19rFmdGAyki
Author: Max Johnson <me@maxj.phd> · 2026-09-05 01:36 UTC
Signed with PGP, not checked
Commit: 07ef2b45cf888c12cf456e58028b2f4dcffe4e30
Parent: f01b1a8
3 files changed, +264 insertions, -240 deletions
M Cargo.lock +13 -13
@@ -4257,7 +4257,7 @@
4257 4257
4258 4258 [[package]]
4259 4259 name = "quasi-declare"
4260 - version = "0.1.6"
4260 + version = "0.1.7"
4261 4261 dependencies = [
4262 4262 "proc-macro2",
4263 4263 "quote",
@@ -7566,6 +7566,18 @@
7566 7566 "winnow 1.0.4",
7567 7567 ]
7568 7568
7569 + [[patch.unused]]
7570 + name = "kberg"
7571 + version = "0.1.0"
7572 +
7573 + [[patch.unused]]
7574 + name = "ops-status"
7575 + version = "0.1.0"
7576 +
7577 + [[patch.unused]]
7578 + name = "painhours"
7579 + version = "0.1.0"
7580 +
7569 7581 [[patch.unused]]
7570 7582 name = "quasi-axum"
7571 7583 version = "0.101.1"
@@ -7594,18 +7606,6 @@
7594 7606 name = "quasi-webview"
7595 7607 version = "0.101.1"
7596 7608
7597 - [[patch.unused]]
7598 - name = "kberg"
7599 - version = "0.1.0"
7600 -
7601 - [[patch.unused]]
7602 - name = "ops-status"
7603 - version = "0.1.0"
7604 -
7605 - [[patch.unused]]
7606 - name = "painhours"
7607 - version = "0.1.0"
7608 -
7609 7609 [[patch.unused]]
7610 7610 name = "quasi-type"
7611 7611 version = "0.1.3"
@@ -343,7 +343,7 @@
343 343 shape screen(state: &Panels<'_>, window: &Window) -> Screen;
344 344
345 345 screen sidebar_content "audiofiles" {
346 - include super::toolbar::body(state);
346 + include super::toolbar::body(&super::toolbar::read(state));
347 347 include super::library::body(&super::library::read(state)) when window.sidebar;
348 348 include super::files::body(&super::files::read(state));
349 349
@@ -85,10 +85,11 @@
85 85 //! host draws it rather than what it holds, so no finding — but a vocabulary
86 86 //! that grows anchoring should know this was the first place it mattered.
87 87
88 - use quasi_router::layout::{self, FieldKind, Priority, Tone, Width};
88 + use quasi_declare::declare;
89 + use quasi_router::layout::{FieldKind, Priority, Tone};
89 90 use quasi_router::{
90 - Act, Action, Choice, Consult, Field, Node, RegionKind, Request, Response, RouteError, Router,
91 - Run, Screen, Slot,
91 + Action, Choice, Consult, Field, Node, RegionKind, Request, Response, RouteError, Router,
92 + Screen, Slot,
92 93 };
93 94
94 95 use super::{Panel, Panels, Where};
@@ -289,346 +290,359 @@
289 290 }
290 291
291 292 /// The toolbar, as a region something else holds.
292 - pub fn body(state: &Panels<'_>) -> Slot {
293 - // A row, said as one. The members were going into the band's body, which is
294 - // a column, and every renderer that read it drew a toolbar one control per
295 - // line; quasi-webview happened to look right because a stylesheet was
296 - // laying the band out and the description was not saying anything.
297 - //
298 - // `Shed` rather than `Wrap` because the shipped bar drops controls when it
299 - // runs out of room rather than growing a second line: the panel toggles
300 - // already carry what each is worth (`Panel::worth`), which is the declared
301 - // replacement for the `screen_w < 900.0` collapse, and a fallback that kept
302 - // everything would leave those ranks saying nothing.
303 - //
304 - // The row is built as a row and then given to the region, which is what
305 - // lets the builders below take a `Run` and add to it. They used to take the
306 - // whole `Slot`, and what they were relying on was that this function had
307 - // already called `across` on it: a promise nothing in their signatures made
308 - // and no compiler could check. `Run::new` is the only way to make a row and
309 - // it takes the fallback, so a helper holding one is holding a row that has
310 - // already said what it does when it is tight.
311 - let bar = Run::new(layout::Fallback::Shed);
312 - let bar = here(bar, state);
313 - let bar = holding(bar, state);
314 - let bar = looking(bar, state);
315 - let bar = panels(bar, state);
316 - Slot::new(BAR, RegionKind::Band).across(bar)
293 + /// The toolbar, read off the app.
294 + ///
295 + /// One read for the whole bar, because the bar is one row: `Fallback::Shed`
296 + /// ranks across everything in it, so what makes room for the search box is a
297 + /// breadcrumb four segments away. Four regions with a run each would shed
298 + /// independently, which is a different bar wearing the same layout.
299 + pub(super) struct Bar {
300 + /// Where you are.
301 + place: Place,
302 + /// What the reader is collecting, while they are collecting anything.
303 + ///
304 + /// Drawn whenever the basket holds anything, in a folder as much as in a
305 + /// walk: the basket is filled while walking but it is not the walk, and a
306 + /// set of samples that vanished when the reader stepped into a folder would
307 + /// be a set they could not trust. Nothing at all when it is empty, because
308 + /// an empty basket is not a query with no results, it is no query.
309 + basket: Option<Basket>,
310 + /// What you are looking for.
311 + looking: Looking,
312 + /// What is showing.
313 + panels: Vec<Toggle>,
314 + /// Whether there is anything to undo.
315 + undoable: bool,
317 316 }
318 317
319 - /// Where you are, which is one of three things.
318 + /// Where you are, which is one of four things said as one shape.
320 319 ///
321 - /// A trail of [`Node::Link`]s rather than acts. Both call a route; the
322 - /// difference is what the reader sees, and `Link`'s own header has the argument:
323 - /// "making every linked value a button would put a row of bevels down the first
324 - /// column of half a dashboard". A breadcrumb is the case that argument was
325 - /// written for.
326 - fn here(bar: Run, state: &Panels<'_>) -> Run {
327 - match state.bar.place() {
328 - Where::Folder { trail } => {
329 - let mut bar = bar.beside(
330 - Node::Link {
331 - text: "/".to_owned(),
332 - action: Action::post("/here/root"),
333 - },
334 - Priority::Essential,
335 - );
336 - for (depth, crumb) in trail.iter().enumerate() {
337 - // The last crumb is where you are, so it goes nowhere. Said as
338 - // prose rather than as a link that does nothing, which is the
339 - // shipped row's `selectable_label(is_last, ..)` made explicit.
340 - if depth + 1 == trail.len() {
341 - bar = bar.beside(
342 - Node::Text {
343 - text: crumb.name.clone(),
344 - tone: Tone::Info,
345 - },
346 - Priority::Essential,
347 - );
348 - } else {
349 - bar = bar.beside(
350 - Node::Link {
351 - text: crumb.name.clone(),
352 - action: Action::post(format!("/here/{}/{}", crumb.id, depth + 1)),
353 - },
354 - // A crumb further from where you are is the first thing
355 - // a narrow bar can lose: the path is still walkable
356 - // from the root, which never drops.
357 - Priority::Optional,
358 - );
359 - }
360 - }
361 - bar
362 - }
363 - // A mode rather than a place, so the way out is a control and not a
364 - // shorter path. The shipped breadcrumb puts Clear in the same segment
365 - // as the label for exactly this reason: "the mode label and the exit
366 - // affordance occupy one row, not two".
367 - Where::Collection { name } => {
368 - leaving(bar, format!("Collection: {name}"), "Back to browsing")
369 - }
370 - // A mode, and a trail inside it. The steps are links for the folder
371 - // crumbs' reason and the way out is an act for the collection's, so
372 - // this is the one place where both are true at once: you can go back
373 - // one sample, or out of walking altogether.
374 - Where::Similar { trail } => {
375 - let last = trail.len().saturating_sub(1);
376 - let mut bar = bar;
377 - for (at, step) in trail.iter().enumerate() {
378 - let says = if at == 0 {
379 - let asked = if step.near_dup {
380 - "Duplicates of"
381 - } else {
382 - "Similar to"
383 - };
384 - format!("{asked}: {}", step.name)
385 - } else {
386 - step.name.clone()
387 - };
388 - if at == last {
389 - bar = bar.beside(
390 - Node::Text {
391 - text: says,
392 - tone: Tone::Info,
393 - },
394 - Priority::Essential,
395 - );
396 - } else {
397 - bar = bar.beside(
398 - Node::Link {
399 - text: says,
400 - action: Action::post(format!("/here/similar/{at}")),
401 - },
402 - Priority::Optional,
403 - );
404 - }
405 - }
406 - bar.beside(
407 - Node::Act(Act::new("Back to browsing", Action::post("/here/leave"))),
408 - Priority::Essential,
409 - )
410 - .beside(
411 - Node::text("Results are ranked by similarity, so column sort is off."),
412 - Priority::Optional,
413 - )
414 - }
415 - // A mode with no trail inside it. You did not arrive here from one
416 - // sample, so there is nowhere in it to go back to; the way out is the
417 - // only way, and the basket itself is beside this on the same bar.
418 - Where::Basket { anchors } => leaving(
419 - bar,
420 - format!("Near all {anchors}"),
421 - "Back to browsing",
422 - )
423 - .beside(
424 - Node::text("Results are ranked by how near they are to the whole basket, so column sort is off."),
425 - Priority::Optional,
426 - ),
427 - }
320 + /// A folder is a trail you walk out of and the three modes are places you leave,
321 + /// and that is the whole of the difference: the steps are the same steps, the
322 + /// mode's label is a step with nowhere to go back to, and only a folder has a
323 + /// root above it.
324 + struct Place {
325 + /// Whether there is a root above the trail.
326 + rooted: bool,
327 + /// The trail, or the mode's label as a trail of one.
328 + steps: Vec<Step>,
329 + /// The way out of a mode, where you are in one.
330 + out: Option<&'static str>,
331 + /// What a mode says about the ordering it forces, where it forces one.
332 + ranked: Option<&'static str>,
428 333 }
429 334
430 - /// What you are looking *for*, which is beside where you are rather than part of
431 - /// it.
432 - ///
433 - /// Drawn whenever the basket holds anything, in a folder as much as in a walk:
434 - /// the basket is filled while walking but it is not the walk, and a set of
435 - /// samples the reader is collecting that vanished when they stepped into a
436 - /// folder would be a set they could not trust.
437 - ///
438 - /// Nothing at all when it is empty. An empty basket is not a query with no
439 - /// results, it is no query, and a control for a question nobody has asked is
440 - /// noise on a bar that already sheds members to fit.
441 - fn holding(bar: Run, state: &Panels<'_>) -> Run {
335 + /// One step of a trail.
336 + struct Step {
337 + says: String,
338 + /// Where stepping back to it is asked for.
339 + ///
340 + /// Nothing on the last one, because the last one is where you are. Said as
341 + /// prose rather than as a link that does nothing, which is the shipped row's
342 + /// `selectable_label(is_last, ..)` made explicit.
343 + back: Option<String>,
344 + }
345 +
346 + /// What the reader is collecting.
347 + struct Basket {
348 + /// Each anchor, which is a thing to stop asking about.
349 + anchors: Vec<Anchor>,
350 + /// What showing them reads, while the answer is not already on screen.
351 + ///
352 + /// Not offered while it is already showing: the answer on screen is the
353 + /// answer to this, and a control that re-asks a question already answered
354 + /// reads as one that would ask a different one.
355 + show: Option<String>,
356 + }
357 +
358 + /// One thing the reader is looking for something near.
359 + struct Anchor {
360 + /// Its position, which is the address that takes it out.
361 + at: usize,
362 + name: String,
363 + }
364 +
365 + /// What you are looking for.
366 + struct Looking {
367 + query: String,
368 + /// Which of the two scopes is chosen.
369 + scope: &'static str,
370 + /// How many results, while a filter is narrowing them.
371 + results: Option<String>,
372 + }
373 +
374 + /// One panel toggle, which is latching because a panel is open or shut.
375 + struct Toggle {
376 + /// The name its address is built from.
377 + at: &'static str,
378 + /// What it says, which carries the filter count on the one toggle that has
379 + /// one.
380 + ///
381 + /// The shipped toggle takes an `Option<usize>` badge for this and nothing
382 + /// else does.
383 + label: String,
384 + /// Whether the panel is open.
385 + on: bool,
386 + /// What it is worth when the bar runs out of room.
387 + ///
388 + /// The declared replacement for the shipped bar's `screen_w < 900.0`
389 + /// collapse: each toggle says what it is worth and `Shed` reads the ranks.
390 + worth: Priority,
391 + /// What it looks like while it is on.
392 + tone: Tone,
393 + }
394 +
395 + /// What the toolbar draws, read off the app.
396 + pub(super) fn read(state: &Panels<'_>) -> Bar {
397 + let searching = state.bar.searching();
442 398 let held = state.bar.basket();
443 - if held.is_empty() {
444 - return bar;
445 - }
446 -
447 - let mut bar = bar.beside(
448 - Node::Text {
449 - text: "Looking for:".to_owned(),
450 - tone: Tone::Info,
451 - },
452 - Priority::Essential,
453 - );
454 - // Each anchor is a link that takes it out, which is the trail's shape turned
455 - // around: a step is somewhere to go back to, and an anchor is something to
456 - // stop asking about. Both are the only thing you can do to one.
457 - for (at, name) in held.iter().enumerate() {
458 - bar = bar.beside(
459 - Node::Link {
460 - text: name.clone(),
461 - action: Action::post(format!("/basket/{at}/remove")),
462 - },
463 - Priority::Optional,
464 - );
465 - }
466 -
467 - // Not offered while it is already showing: the answer on screen is the
468 - // answer to this, and a control that re-asks a question already answered
469 - // reads as one that would ask a different one.
470 - if !state.bar.basket_showing() {
471 - bar = bar.beside(
472 - Node::Act(Act::new(
399 + let showing = state.bar.showing();
400 + Bar {
401 + place: place(state.bar.place()),
402 + basket: (!held.is_empty()).then(|| Basket {
403 + anchors: held
404 + .iter()
405 + .enumerate()
406 + .map(|(at, name)| Anchor {
407 + at,
408 + name: name.clone(),
409 + })
410 + .collect(),
411 + show: (!state.bar.basket_showing()).then(|| {
473 412 if held.len() == 1 {
474 413 "Show what is near it".to_owned()
475 414 } else {
476 415 format!("Show what is near all {}", held.len())
477 - },
478 - Action::post("/basket/show"),
479 - )),
480 - Priority::Essential,
481 - );
482 - }
483 -
484 - bar.beside(
485 - Node::Act(Act::new("Empty the basket", Action::post("/basket/clear"))),
486 - Priority::Optional,
487 - )
488 - }
489 -
490 - /// A mode you are in, and the way out of it.
491 - fn leaving(bar: Run, says: String, out: &str) -> Run {
492 - bar.beside(
493 - Node::Text {
494 - text: says,
495 - tone: Tone::Info,
416 + }
417 + }),
418 + }),
419 + looking: Looking {
420 + query: searching.query.clone(),
421 + scope: if searching.everywhere {
422 + "all"
423 + } else {
424 + "folder"
425 + },
426 + results: searching.filtered.then(|| searching.results.to_string()),
496 427 },
497 - Priority::Essential,
498 - )
499 - .beside(
500 - Node::Act(Act::new(out, Action::post("/here/leave"))),
501 - Priority::Essential,
502 - )
428 + panels: Panel::ALL
429 + .into_iter()
430 + .map(|panel| {
431 + let on = showing.contains(&panel);
432 + Toggle {
433 + at: panel.as_str(),
434 + label: if panel == Panel::Filters && searching.filters > 0 {
435 + format!("{} ({})", panel.label(), searching.filters)
436 + } else {
437 + panel.label().to_owned()
438 + },
439 + on,
440 + worth: panel.worth(),
441 + tone: if on { Tone::Info } else { Tone::Neutral },
442 + }
443 + })
444 + .collect(),
445 + undoable: state.bar.undoable(),
446 + }
503 447 }
504 448
505 - /// What you are looking for.
506 - fn looking(bar: Run, state: &Panels<'_>) -> Run {
507 - let searching = state.bar.searching();
449 + /// Where you are, as the one shape the four places share.
450 + fn place(place: Where) -> Place {
451 + match place {
452 + Where::Folder { trail } => Place {
453 + rooted: true,
454 + steps: steps(trail.len(), |at| {
455 + (
456 + trail[at].name.clone(),
457 + format!("/here/{}/{}", trail[at].id, at + 1),
458 + )
459 + }),
460 + out: None,
461 + ranked: None,
462 + },
463 + // A mode rather than a place, so the way out is a control and not a
464 + // shorter path. The shipped breadcrumb puts Clear in the same segment as
465 + // the label for exactly this reason: the mode label and the exit
466 + // affordance occupy one row, not two.
467 + Where::Collection { name } => moded(format!("Collection: {name}"), None),
468 + // A mode, and a trail inside it. The steps are links for the folder
469 + // crumbs' reason and the way out is an act for the collection's, so this
470 + // is the one place both are true at once: you can go back one sample, or
471 + // out of walking altogether.
472 + Where::Similar { trail } => Place {
473 + rooted: false,
474 + steps: steps(trail.len(), |at| {
475 + let says = if at == 0 {
476 + let asked = if trail[at].near_dup {
477 + "Duplicates of"
478 + } else {
479 + "Similar to"
480 + };
481 + format!("{asked}: {}", trail[at].name)
482 + } else {
483 + trail[at].name.clone()
484 + };
485 + (says, format!("/here/similar/{at}"))
486 + }),
487 + out: Some("Back to browsing"),
488 + ranked: Some("Results are ranked by similarity, so column sort is off."),
489 + },
490 + // A mode with no trail inside it. You did not arrive here from one
491 + // sample, so there is nowhere in it to go back to; the way out is the
492 + // only way, and the basket itself is beside this on the same bar.
493 + Where::Basket { anchors } => moded(
494 + format!("Near all {anchors}"),
495 + Some(
496 + "Results are ranked by how near they are to the whole basket, \
497 + so column sort is off.",
498 + ),
499 + ),
500 + }
501 + }
508 502
509 - let mut bar = bar
510 - .beside(
511 - Node::Field(Box::new(
512 - Field::new(FieldKind::Text, QUERY, "Search")
513 - .value(&searching.query)
514 - .hint("Search samples...")
515 - // What `trailing_width` was measuring for, said instead of
516 - // measured. quasi 0.17.0, settled by Max: fill is determined at
517 - // the description stage. It is the default, so this line changes
518 - // no pixel -- and it is the difference between a row that
519 - // happens to look right and one that says what it means.
520 - .width(Width::Fill)
521 - // A question and not a write: it narrows what the browser
522 - // is showing and nothing is stored. The wait is the typing
523 - // wait, so a query typed in one go asks once.
524 - .consulting(Consult::new(Action::post("/search"))),
525 - )),
526 - Priority::Essential,
527 - )
528 - .beside(
503 + /// A mode, which is a trail of one step you cannot walk back into.
504 + fn moded(says: String, ranked: Option<&'static str>) -> Place {
505 + Place {
506 + rooted: false,
507 + steps: vec![Step { says, back: None }],
508 + out: Some("Back to browsing"),
509 + ranked,
510 + }
511 + }
512 +
513 + /// A trail, with the last step going nowhere because it is where you are.
514 + fn steps(count: usize, say: impl Fn(usize) -> (String, String)) -> Vec<Step> {
515 + let last = count.saturating_sub(1);
516 + (0..count)
517 + .map(|at| {
518 + let (says, back) = say(at);
519 + Step {
520 + says,
521 + back: (at != last).then_some(back),
522 + }
523 + })
524 + .collect()
525 + }
526 +
527 + declare! {
528 + /// The toolbar, as a region something else holds.
529 + ///
530 + /// A row, said as one. The members were going into the band's body, which is
531 + /// a column, and every renderer that read it drew a toolbar one control per
532 + /// line; quasi-webview happened to look right because a stylesheet was
533 + /// laying the band out and the description was not saying anything.
534 + pub(super) shape body(bar: &Bar) -> Slot;
535 +
536 + region BAR as Band {
537 + // `Shed` rather than `Wrap` because the shipped bar drops controls when
538 + // it runs out of room rather than growing a second line, and every
539 + // member below says what it is worth.
540 + //
541 + // One run and not four. The ranks are read across the whole bar, so what
542 + // makes room for the search box is a breadcrumb four segments away; four
543 + // runs would each shed alone and no far crumb would ever pay for a near
544 + // control.
545 + across Shed {
546 + // Where you are. A trail of `Link`s rather than acts: `Link`'s own
547 + // header has the argument, that "making every linked value a button
548 + // would put a row of bevels down the first column of half a
549 + // dashboard", and a breadcrumb is the case it was written for.
550 + //
551 + // The root never drops, so the path stays walkable from it however
552 + // narrow the window gets.
Lines truncated