Skip to main content

max / audiofiles

Quick import folder asks through Outcome::Locate The quick door says a folder is wanted and the answer lands on /import/open/quick/chosen, which indexes it. The intent that opened the picker is gone, and the module header now names two converted doors.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-26 00:31 UTC
Signed with PGP, not checked
Commit: 40e666348188448dc7f35e13af4b400f83a96186
Parent: 3d5a6c6
4 files changed, +62 insertions, -25 deletions
@@ -81,18 +81,19 @@
81 81 //! | invalid-tag warning per folder | yes | `tags::validate_tag` over what was typed, and pure |
82 82 //! | BPM range and top keys over the batch | yes | arithmetic over the results the screen already carries |
83 83 //! | **rate and ETA** | no | a rolling wall-clock buffer, which is a fact about this machine |
84 - //! | **`Import files...`**, and two doors not converted yet | no | see below |
84 + //! | **`Import files...`**, and the source door not converted yet | no | see below |
85 85 //! | **↑/↓ walking the review list** | no | a key bound to "the next row of a list", which `Chrome` cannot say |
86 86 //!
87 87 //! The pickers are the sharper half. `Import folder...`, `Quick import
88 88 //! folder...`, `Import files...` and `Change...` each open a native dialog and
89 89 //! then act, which this header filed as consumers five to eight of
90 90 //! `quasi:vocabulary:host-save-location`. That gap closed in quasi 0.60
91 - //! (`ec92f9cb`) and `Import folder...` is converted: [`open_folder`](fn@open_folder)
92 - //! says a folder is wanted and [`folder_chosen`](fn@folder_chosen) sets the
93 - //! wizard up on the answer.
91 + //! (`ec92f9cb`) and the two folder doors are converted:
92 + //! [`open_folder`](fn@open_folder) and [`open_quickly`](fn@open_quickly) say a
93 + //! folder is wanted, and the answer lands on a second address that does the
94 + //! work.
94 95 //!
95 - //! The other three still hand off, and a route that hands off to the host has
96 + //! The other two still hand off, and a route that hands off to the host has
96 97 //! nothing true to answer. [`Outcome`](quasi_router::Outcome) is `Screen`,
97 98 //! `Fragment`, `Goto` or `Over`, and none of them is "nothing here changed".
98 99 //! They answer `Goto(/import)`, right by the time the picker returns and a
@@ -223,6 +224,7 @@
223 224 .post("/import/open/folder", open_folder)
224 225 .post("/import/open/folder/chosen", folder_chosen)
225 226 .post("/import/open/quick", open_quickly)
227 + .post("/import/open/quick/chosen", quick_chosen)
226 228 .post("/import/open/files", open_files)
227 229 .post("/import/source", change_source)
228 230 .post("/import/set/{decision}", decide)
@@ -400,8 +402,26 @@
400 402 }
401 403
402 404 /// `POST /import/open/quick`
403 - fn open_quickly(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> {
404 - state.importing.open_quickly();
405 + ///
406 + /// Says a folder is wanted and leaves the picker to the host. Indexing it is
407 + /// [`quick_chosen`](fn@quick_chosen)'s, on the answer.
408 + fn open_quickly(_state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> {
409 + Ok(Response::locate(Locating::folder(
410 + "Quick import folder",
411 + Action::post("/import/open/quick/chosen"),
412 + FOLDER,
413 + )))
414 + }
415 +
416 + /// `POST /import/open/quick/chosen`
417 + ///
418 + /// Empty means the reader backed out of the picker, which is the same second
419 + /// guard [`folder_chosen`](fn@folder_chosen) carries.
420 + fn quick_chosen(state: &Panels<'_>, request: Request) -> Result<Response, RouteError> {
421 + let folder = request.payload.get(FOLDER).unwrap_or_default();
422 + if !folder.is_empty() {
423 + state.importing.open_quickly(folder);
424 + }
405 425 Ok(handed_off())
406 426 }
407 427
@@ -842,8 +842,8 @@
842 842 CancelImport,
843 843 /// Set the wizard up on this folder, which the reader picked.
844 844 ImportFolder(std::path::PathBuf),
845 - /// Ask the host for a folder, then index it with no questions asked.
846 - OpenQuickImport,
845 + /// Index this folder with no questions asked, the reader having picked it.
846 + QuickImportFolder(std::path::PathBuf),
847 847 /// Ask the host for files, then merge them into the vault that is open.
848 848 OpenImportFiles,
849 849 /// Ask the host for a different folder for the import being configured.
@@ -3802,8 +3802,8 @@
3802 3802 /// Set the wizard up on this folder.
3803 3803 fn open_folder(&self, folder: &str);
3804 3804
3805 - /// Pick a folder, then index it with no questions asked.
3806 - fn open_quickly(&self);
3805 + /// Index this folder with no questions asked.
3806 + fn open_quickly(&self, folder: &str);
3807 3807
3808 3808 /// Pick files, then merge them into the vault that is open.
3809 3809 fn open_files(&self);
@@ -4097,8 +4097,8 @@
4097 4097 self.push(Intent::ImportFolder(std::path::PathBuf::from(folder)));
4098 4098 }
4099 4099
4100 - fn open_quickly(&self) {
4101 - self.push(Intent::OpenQuickImport);
4100 + fn open_quickly(&self, folder: &str) {
4101 + self.push(Intent::QuickImportFolder(std::path::PathBuf::from(folder)));
4102 4102 }
4103 4103
4104 4104 fn open_files(&self) {
@@ -1049,11 +1049,7 @@
1049 1049 // `OpenImportFiles` opens its own, for the reason `importing`'s
1050 1050 // header gives.
1051 1051 Intent::ImportFolder(folder) => state.show_import_options(folder),
1052 - Intent::OpenQuickImport => {
1053 - state
1054 - .dialogs
1055 - .pick_folder("Quick import folder", BrowserState::quick_import_folder);
1056 - }
1052 + Intent::QuickImportFolder(folder) => state.quick_import_folder(folder),
1057 1053 Intent::OpenImportFiles => {
1058 1054 state.dialogs.pick_files(
1059 1055 "Import files",
@@ -5756,7 +5756,7 @@
5756 5756 fn accept(&self, _again: bool) {}
5757 5757 fn cancel(&self) {}
5758 5758 fn open_folder(&self, _folder: &str) {}
5759 - fn open_quickly(&self) {}
5759 + fn open_quickly(&self, _folder: &str) {}
5760 5760 fn open_files(&self) {}
5761 5761 fn change_source(&self) {}
5762 5762 fn decide(&self, _decision: Decision, _value: &str) {}
@@ -6416,8 +6416,8 @@
6416 6416 self.say(format!("open:folder {folder}"));
6417 6417 }
6418 6418
6419 - fn open_quickly(&self) {
6420 - self.say("open:quick");
6419 + fn open_quickly(&self, folder: &str) {
6420 + self.say(format!("open:quick {folder}"));
6421 6421 }
6422 6422
6423 6423 fn open_files(&self) {
@@ -8158,10 +8158,7 @@
8158 8158 fn the_doors_hand_off_to_the_host_and_say_where_the_answer_will_be() {
8159 8159 // No outcome means "nothing here changed", so they answer the flow, which
8160 8160 // is right by the time the picker returns. See the module header.
8161 - for (address, expected) in [
8162 - ("/import/open/quick", "open:quick"),
8163 - ("/import/open/files", "open:files"),
8164 - ] {
8161 + for (address, expected) in [("/import/open/files", "open:files")] {
8165 8162 let import = FakeImport::default();
8166 8163 let response = importing(&import, Request::post(address)).unwrap();
8167 8164 assert!(
@@ -8199,6 +8196,30 @@
8199 8196 assert!(import.answered().is_empty());
8200 8197 }
8201 8198
8199 + #[test]
8200 + fn the_quick_door_asks_the_host_for_a_folder_and_indexes_the_answer() {
8201 + let import = FakeImport::default();
8202 + let response = importing(&import, Request::post("/import/open/quick")).unwrap();
8203 + let Outcome::Locate(asking) = &response.outcome else {
8204 + panic!("expected an ask, got {:?}", response.outcome);
8205 + };
8206 + assert_eq!(asking.answers.route(), Some("/import/open/quick/chosen"));
8207 + assert_eq!(asking.sought, quasi_router::Sought::Folder);
8208 + assert!(import.answered().is_empty(), "the ask indexed something");
8209 +
8210 + let answered = Request::post("/import/open/quick/chosen")
8211 + .sending(Params::new().with("folder".to_owned(), "/mnt/kit".to_owned()));
8212 + importing(&import, answered).unwrap();
8213 + assert_eq!(import.answered(), ["open:quick /mnt/kit"]);
8214 + }
8215 +
8216 + #[test]
8217 + fn backing_out_of_the_quick_picker_indexes_nothing() {
8218 + let import = FakeImport::default();
8219 + importing(&import, Request::post("/import/open/quick/chosen")).unwrap();
8220 + assert!(import.answered().is_empty());
8221 + }
8222 +
8202 8223 #[test]
8203 8224 fn the_source_can_only_be_changed_while_there_is_one_being_configured() {
8204 8225 let configuring = FakeImport::at(configuring(Strategy::Flat, "", &[]));