max / audiofiles
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01P8ostB2UmZJGj5WjSHRSot
6 files changed,
+65 insertions,
-68 deletions
| @@ -554,9 +554,12 @@ | |||
| 554 | 554 | declare! { | |
| 555 | 555 | /// The move modal. | |
| 556 | 556 | /// | |
| 557 | - | /// A table of one column rather than a list, because picking a row is what | |
| 558 | - | /// this screen is for and `Cells::activate` is what says a row is | |
| 559 | - | /// pressable. The shipped modal draws `selectable_label` per directory with | |
| 557 | + | /// A table of one column rather than a list, which since the 2026-09-05 | |
| 558 | + | /// collapse is a presentation choice and nothing more: both draw the same | |
| 559 | + | /// [`Row`](quasi_router::Row), and `Row::activate` is what says a row is | |
| 560 | + | /// pressable either way. A table because a destination is a record the | |
| 561 | + | /// reader is picking from a set, not a feed. | |
| 562 | + | /// The shipped modal draws `selectable_label` per directory with | |
| 560 | 563 | /// a substring filter above it; the filter is not described, on the rule the | |
| 561 | 564 | /// tag completions follow -- narrowing a list while someone types is what a | |
| 562 | 565 | /// host does with a list it was handed. |
| @@ -51,9 +51,9 @@ | |||
| 51 | 51 | //! has to become a context menu, an action sheet and a key-driven menu, and no | |
| 52 | 52 | //! single renderer can be where that is said. It is right, and the file list is | |
| 53 | 53 | //! a [`Node::Table`], whose | |
| 54 | - | //! [`Cells`](quasi_router::Cells) had no `menu`. | |
| 54 | + | //! [`Row`](quasi_router::Row) had no `menu`. | |
| 55 | 55 | //! | |
| 56 | - | //! **[`Cells::menu`](quasi_router::Cells::menu) exists as of quasi-router 0.20.0, and this screen describes | |
| 56 | + | //! **[`Row::menu`](quasi_router::Row::menu) exists as of quasi-router 0.20.0, and this screen describes | |
| 57 | 57 | //! its row menu through it.** See `menu` below. A member is added when a consumer | |
| 58 | 58 | //! asks for it, not because its sibling has one. | |
| 59 | 59 | //! | |
| @@ -62,7 +62,7 @@ | |||
| 62 | 62 | //! every row gets the sample columns and a Play control, folders included. | |
| 63 | 63 | //! | |
| 64 | 64 | //! The one entry still not described is **Add to Collection**, the only nested | |
| 65 | - | //! one. `Cells::menu` is flat, matching `Row::menu`, and a submenu wants a second | |
| 65 | + | //! one. `Row::menu` is flat, and a submenu wants a second | |
| 66 | 66 | //! consumer before [`Act`] grows a child list. Flattening it reads "Add to | |
| 67 | 67 | //! Kicks", "Add to Breaks" for as many collections as exist, which is honest and | |
| 68 | 68 | //! gets long. | |
| @@ -90,7 +90,7 @@ | |||
| 90 | 90 | //! on one. Picking eleven kicks out of four hundred by hand is close to what this | |
| 91 | 91 | //! app is for, so the selection has a described shape. | |
| 92 | 92 | //! | |
| 93 | - | //! It is two members. `Cells::chosen` is the | |
| 93 | + | //! It is two members. `Row::chosen` is the | |
| 94 | 94 | //! third state beside `current` and `selected`, three-valued for the reason the | |
| 95 | 95 | //! tick is: a ctrl-click lands on an **unchosen** row, so a renderer has to know | |
| 96 | 96 | //! that row can join a set before anything is in one. |
| @@ -584,7 +584,7 @@ | |||
| 584 | 584 | /// The file list holds both, and a described row has to tell them apart or | |
| 585 | 585 | /// every row gets the sample columns and a Play control, folders included. | |
| 586 | 586 | /// `draw_context_menu` branches on exactly this and offers two different | |
| 587 | - | /// menus, so `Cells::menu` cannot be described without it. | |
| 587 | + | /// menus, so `Row::menu` cannot be described without it. | |
| 588 | 588 | pub directory: bool, | |
| 589 | 589 | /// Whether the bytes are only in the cloud. | |
| 590 | 590 | /// | |
| @@ -601,11 +601,11 @@ | |||
| 601 | 601 | /// unchosen one. What reads it is `files::menu`, which offers a row inside a | |
| 602 | 602 | /// multi-selection what can be done to the *set* rather than to the row. | |
| 603 | 603 | /// | |
| 604 | - | /// It is also what the list *draws*. Until quasi 0.78 nothing could: `Cells` | |
| 604 | + | /// It is also what the list *draws*. Until quasi 0.78 nothing could: a row | |
| 605 | 605 | /// marked the current row and held a tick, a tick is a staged set awaiting a | |
| 606 | 606 | /// commit control rather than a selection already in force, and a | |
| 607 | 607 | /// five-hundred-row Cmd+A therefore looked exactly like a one-row click. | |
| 608 | - | /// `Cells::chosen` is the third state that closes it, and `Cells::choosing` | |
| 608 | + | /// `Row::chosen` is the third state that closes it, and `Row::choosing` | |
| 609 | 609 | /// is what `files::row` writes this into. | |
| 610 | 610 | pub selected: bool, | |
| 611 | 611 | /// Whether this sample is in the basket -- one of the samples being looked |
| @@ -197,7 +197,7 @@ | |||
| 197 | 197 | /// Raise the empty-space menu when a press lands on no row. | |
| 198 | 198 | /// | |
| 199 | 199 | /// The second host gesture, and the smaller half of audiofiles `41996627`. A | |
| 200 | - | /// row answers a menu because a row is a described thing and `Cells::menu` says | |
| 200 | + | /// row answers a menu because a row is a described thing and `Row::menu` says | |
| 201 | 201 | /// what it offers; **the absence of a row is not a described thing**, so nothing | |
| 202 | 202 | /// in a description can answer a press on the part of the list that is not one. | |
| 203 | 203 | /// The renderer is the only side that knows -- it drew the rows and it knows | |
| @@ -2589,7 +2589,7 @@ | |||
| 2589 | 2589 | /// | |
| 2590 | 2590 | /// - **What the press chose.** Pressing an unchosen row and dragging drags | |
| 2591 | 2591 | /// *that* row: the selection collapses onto the row under the cursor first. | |
| 2592 | - | /// The rows carry a `Cells::value`, so `row_at` answers with the sample itself | |
| 2592 | + | /// The rows carry a `Row::value`, so `row_at` answers with the sample itself | |
| 2593 | 2593 | /// and the app looks it up in its own contents, rather than matching a position | |
| 2594 | 2594 | /// back against the ordering the description happened to be built from, which | |
| 2595 | 2595 | /// is the same row identified twice and agreeing by luck. |
| @@ -303,36 +303,13 @@ | |||
| 303 | 303 | out.addresses.push(reorder.destination.as_str().to_owned()); | |
| 304 | 304 | } | |
| 305 | 305 | } | |
| 306 | - | for cells in rows { | |
| 307 | - | if let Some(activate) = &cells.activate { | |
| 308 | - | out.push(Role::Button, first_words(&cells.values), false); | |
| 309 | - | out.addresses.push(activate.destination.as_str().to_owned()); | |
| 310 | - | } | |
| 311 | - | for cell in &cells.values { | |
| 312 | - | for part in &cell.parts { | |
| 313 | - | walk(part, out); | |
| 314 | - | } | |
| 315 | - | } | |
| 306 | + | for row in rows { | |
| 307 | + | row_offers(row, out); | |
| 316 | 308 | } | |
| 317 | 309 | } | |
| 318 | 310 | Node::List { rows, .. } => { | |
| 319 | 311 | for row in rows { | |
| 320 | - | // A row is claimed when it opens OR when it offers a menu, | |
| 321 | - | // which is `quasi_immediate::node::row`'s own condition: a row | |
| 322 | - | // that only offers a menu still needs somewhere to right-click, | |
| 323 | - | // and it is announced by its first text either way. The menu's | |
| 324 | - | // own acts are not offers here -- they are not on screen until | |
| 325 | - | // the gesture -- but the row that carries them is. | |
| 326 | - | if row.activate.is_some() || !row.menu.is_empty() { | |
| 327 | - | let named: Vec<_> = row.parts.iter().map(|part| part.node.clone()).collect(); | |
| 328 | - | out.push(Role::Button, first_text(&named), false); | |
| 329 | - | } | |
| 330 | - | if let Some(activate) = &row.activate { | |
| 331 | - | out.addresses.push(activate.destination.as_str().to_owned()); | |
| 332 | - | } | |
| 333 | - | for part in &row.parts { | |
| 334 | - | walk(&part.node, out); | |
| 335 | - | } | |
| 312 | + | row_offers(row, out); | |
| 336 | 313 | } | |
| 337 | 314 | } | |
| 338 | 315 | Node::Region(slot) => { | |
| @@ -396,12 +373,39 @@ | |||
| 396 | 373 | cells | |
| 397 | 374 | .iter() | |
| 398 | 375 | .find_map(|cell| { | |
| 399 | - | let said = first_text(&cell.parts); | |
| 376 | + | let said = first_text(&cell.content); | |
| 400 | 377 | (!said.is_empty()).then_some(said) | |
| 401 | 378 | }) | |
| 402 | 379 | .unwrap_or_default() | |
| 403 | 380 | } | |
| 404 | 381 | ||
| 382 | + | /// What one row offers, whichever container it sits in. | |
| 383 | + | /// | |
| 384 | + | /// A list row and a table row are one type since the 2026-09-05 collapse, and | |
| 385 | + | /// this is written once for the reason `quasi_immediate::node::row_name` is: | |
| 386 | + | /// the two copies had already drifted. The table copy claimed a row only when | |
| 387 | + | /// it opened, while the shipped table hangs a menu off every cell's response, | |
| 388 | + | /// so a table row that offered nothing but a menu was an offer this side could | |
| 389 | + | /// not see. | |
| 390 | + | fn row_offers(row: &quasi_router::Row, out: &mut Offering) { | |
| 391 | + | // A row is claimed when it opens OR when it offers a menu, which is | |
| 392 | + | // `quasi_immediate::node::row`'s own condition: a row that only offers a | |
| 393 | + | // menu still needs somewhere to right-click, and it is announced by its | |
| 394 | + | // first text either way. The menu's own acts are not offers here -- they | |
| 395 | + | // are not on screen until the gesture -- but the row that carries them is. | |
| 396 | + | if row.activate.is_some() || !row.menu.is_empty() { | |
| 397 | + | out.push(Role::Button, first_words(&row.cells), false); | |
| 398 | + | } | |
| 399 | + | if let Some(activate) = &row.activate { | |
| 400 | + | out.addresses.push(activate.destination.as_str().to_owned()); | |
| 401 | + | } | |
| 402 | + | for cell in &row.cells { | |
| 403 | + | for node in &cell.content { | |
| 404 | + | walk(node, out); | |
| 405 | + | } | |
| 406 | + | } | |
| 407 | + | } | |
| 408 | + | ||
| 405 | 409 | /// The first thing a run of leaves says. | |
| 406 | 410 | fn first_text(parts: &[Node]) -> String { | |
| 407 | 411 | parts |
| @@ -1949,7 +1949,7 @@ | |||
| 1949 | 1949 | // Peak is off in the fixture, so it is not described at all. | |
| 1950 | 1950 | assert_eq!(names, ["Name", "Duration", "BPM", "Key", "Tags", "Play"]); | |
| 1951 | 1951 | assert_eq!(rows.len(), 1); | |
| 1952 | - | assert_eq!(rows[0].values.len(), columns.len(), "a cell per column"); | |
| 1952 | + | assert_eq!(rows[0].cells.len(), columns.len(), "a cell per column"); | |
| 1953 | 1953 | } | |
| 1954 | 1954 | ||
| 1955 | 1955 | #[test] | |
| @@ -2067,7 +2067,7 @@ | |||
| 2067 | 2067 | ||
| 2068 | 2068 | #[test] | |
| 2069 | 2069 | fn a_sample_row_offers_the_menu_the_shipped_one_offers() { | |
| 2070 | - | // `Cells::menu`, the member this screen asked quasi-router for. The | |
| 2070 | + | // `Row::menu`, the member this screen asked quasi-router for. The | |
| 2071 | 2071 | // assertion is against `draw_context_menu`'s sample branch: same acts, same | |
| 2072 | 2072 | // order, same words. | |
| 2073 | 2073 | let files = FakeFiles::with(vec![sample(7, "kick.wav")]); | |
| @@ -2130,13 +2130,9 @@ | |||
| 2130 | 2130 | ||
| 2131 | 2131 | // And a folder still fills every column, because cells are positional: the | |
| 2132 | 2132 | // Play cell is empty rather than missing. | |
| 2133 | - | assert_eq!(rows[0].values.len(), columns.len()); | |
| 2133 | + | assert_eq!(rows[0].cells.len(), columns.len()); | |
| 2134 | 2134 | assert!( | |
| 2135 | - | !rows[0] | |
| 2136 | - | .values | |
| 2137 | - | .last() | |
| 2138 | - | .expect("a Play cell") | |
| 2139 | - | .carries_control(), | |
| 2135 | + | !rows[0].cells.last().expect("a Play cell").carries_control(), | |
| 2140 | 2136 | "a folder offered something to play" | |
| 2141 | 2137 | ); | |
| 2142 | 2138 | } | |
| @@ -2195,9 +2191,9 @@ | |||
| 2195 | 2191 | "a basket answer is already ranked, so the column does not offer a sort" | |
| 2196 | 2192 | ); | |
| 2197 | 2193 | ||
| 2198 | - | let tokens = |row: &quasi_router::Cells| -> Vec<String> { | |
| 2199 | - | row.values[1] | |
| 2200 | - | .parts | |
| 2194 | + | let tokens = |row: &quasi_router::Row| -> Vec<String> { | |
| 2195 | + | row.cells[1] | |
| 2196 | + | .content | |
| 2201 | 2197 | .iter() | |
| 2202 | 2198 | .filter_map(|part| match part { | |
| 2203 | 2199 | Node::Token(tag) => Some(tag.label.clone()), | |
| @@ -2219,7 +2215,7 @@ | |||
| 2219 | 2215 | ||
| 2220 | 2216 | assert!(columns.iter().all(|column| column.name != "Near")); | |
| 2221 | 2217 | assert_eq!( | |
| 2222 | - | rows[0].values.len(), | |
| 2218 | + | rows[0].cells.len(), | |
| 2223 | 2219 | columns.len(), | |
| 2224 | 2220 | "cells are positional against the columns" | |
| 2225 | 2221 | ); | |
| @@ -3581,9 +3577,9 @@ | |||
| 3581 | 3577 | } | |
| 3582 | 3578 | ||
| 3583 | 3579 | /// The text of one cell in a table row. | |
| 3584 | - | fn cell_text(row: &quasi_router::Cells, at: usize) -> String { | |
| 3585 | - | row.values[at] | |
| 3586 | - | .parts | |
| 3580 | + | fn cell_text(row: &quasi_router::Row, at: usize) -> String { | |
| 3581 | + | row.cells[at] | |
| 3582 | + | .content | |
| 3587 | 3583 | .iter() | |
| 3588 | 3584 | .filter_map(|node| match node { | |
| 3589 | 3585 | Node::Text { text, .. } => Some(text.clone()), | |
| @@ -3594,10 +3590,8 @@ | |||
| 3594 | 3590 | ||
| 3595 | 3591 | /// A row's trailing fact, if it has one. | |
| 3596 | 3592 | fn meta_of(row: &quasi_router::Row) -> Option<String> { | |
| 3597 | - | row.parts | |
| 3598 | - | .iter() | |
| 3599 | - | .find(|part| part.role == quasi_router::layout::RowPart::Meta) | |
| 3600 | - | .and_then(|part| match &part.node { | |
| 3593 | + | row.role(quasi_router::layout::RowPart::Meta) | |
| 3594 | + | .find_map(|node| match node { | |
| 3601 | 3595 | Node::Text { text, .. } => Some(text.clone()), | |
| 3602 | 3596 | _ => None, | |
| 3603 | 3597 | }) | |
| @@ -4048,7 +4042,7 @@ | |||
| 4048 | 4042 | panic!("expected the preview table"); | |
| 4049 | 4043 | }; | |
| 4050 | 4044 | for row in rows { | |
| 4051 | - | let marked = row.values[1].parts.iter().any(|part| { | |
| 4045 | + | let marked = row.cells[1].content.iter().any(|part| { | |
| 4052 | 4046 | matches!(part, Node::Token(tag) if tag.tone == quasi_router::layout::Tone::Warning) | |
| 4053 | 4047 | }); | |
| 4054 | 4048 | assert!(marked, "a colliding name is not marked"); | |
| @@ -4433,10 +4427,8 @@ | |||
| 4433 | 4427 | ||
| 4434 | 4428 | /// A row's primary part. | |
| 4435 | 4429 | fn primary_of(row: &quasi_router::Row) -> Option<String> { | |
| 4436 | - | row.parts | |
| 4437 | - | .iter() | |
| 4438 | - | .find(|part| part.role == quasi_router::layout::RowPart::Primary) | |
| 4439 | - | .and_then(|part| match &part.node { | |
| 4430 | + | row.role(quasi_router::layout::RowPart::Primary) | |
| 4431 | + | .find_map(|node| match node { | |
| 4440 | 4432 | Node::Text { text, .. } => Some(text.clone()), | |
| 4441 | 4433 | _ => None, | |
| 4442 | 4434 | }) | |
| @@ -5228,10 +5220,8 @@ | |||
| 5228 | 5220 | let marks: Vec<String> = rows | |
| 5229 | 5221 | .iter() | |
| 5230 | 5222 | .flat_map(|row| { | |
| 5231 | - | row.parts | |
| 5232 | - | .iter() | |
| 5233 | - | .filter(|part| part.role == quasi_router::layout::RowPart::Tokens) | |
| 5234 | - | .filter_map(|part| match &part.node { | |
| 5223 | + | row.role(quasi_router::layout::RowPart::Tokens) | |
| 5224 | + | .filter_map(|node| match node { | |
| 5235 | 5225 | Node::Token(tag) => Some(tag.label.clone()), | |
| 5236 | 5226 | _ => None, | |
| 5237 | 5227 | }) | |
| @@ -8347,7 +8337,7 @@ | |||
| 8347 | 8337 | ||
| 8348 | 8338 | let tagged: Vec<String> = deep_rows(&screen) | |
| 8349 | 8339 | .into_iter() | |
| 8350 | - | .filter_map(|row| row.parts.first().map(|_| row.primary())) | |
| 8340 | + | .filter_map(|row| row.cells.first().map(|_| row.primary())) | |
| 8351 | 8341 | .collect(); | |
| 8352 | 8342 | assert!( | |
| 8353 | 8343 | tagged.iter().any(|said| said.contains("drums/kick")), |