The index pass belongs to the view model, not to the keystroke that started it

Reported: the index looked recreated on every keystroke, and switching library
started it over. Both are the same bug, and it is in the ownership of the pass.

searchEverywhere cancels and re-creates searchJob on every keystroke, and
loadLibrary does the same with libraryJob. bringIndexCurrent was a suspend
function called from inside those jobs, so the walk ran in the job that the next
interaction cancelled. A cancelled pass never reaches endVolume, so the volume's
stamp stayed old, so the next interaction found the index stale and started
again from the first folder. With a 20 000-file volume on a phone, where a walk
takes tens of seconds and typing takes milliseconds, the index could not finish
while anyone was typing - and the progress counter restarting with every
character was the visible half of it.

The pass now has its own job on the view model: one at a time, shared by
whoever asks, not cancelled by the screen that asked. Search does not wait for
it once there is an answer to show (it did before, which queued the UI behind
the walk), and when the pass lands it re-answers whatever is on screen - the
query or the library - so "I just downloaded it" still works. A rebuild cancels
any pass in flight and takes over: it is the one action that recovers from the
index being wrong rather than old.

Three tests, each standing inside a walk through a gated StorageSource: typing
does not start a second pass, switching library does not start a second pass, and
a finished pass answers the search again when it finds the file. 430 tests total.
This commit is contained in:
2026-10-18 10:50:39 +02:00
parent 14057ca4ce
commit 7dcd2bc0f0
4 changed files with 176 additions and 14 deletions
+6 -3
View File
@@ -2,7 +2,7 @@
Generated from the source by `tools/make-code-map.py` - do not edit by hand. Regenerate it after a change (`python3 tools/make-code-map.py`); `--check` exits non-zero when this file is stale.
80 source files, 13841 lines - 37 test files, 427 tests. The behaviour index at the end lists every test name, and in this project a test name is a sentence about what the app does, so it doubles as the specification.
80 source files, 13899 lines - 37 test files, 430 tests. The behaviour index at the end lists every test name, and in this project a test name is a sentence about what the app does, so it doubles as the specification.
Start at `AGENTS.md`. What is done, and why: `docs/ROADMAP.md` and `CHANGELOG.md`. What was verified by hand rather than by a test: `docs/EDGE_CASE_TESTING.md`.
@@ -104,7 +104,7 @@ The browser screen: view model, state, list and grid, dialogs, selection bar
| `BrowserScreen.kt` | 661 | The browser, and everything that can be on top of it. BrowserScreen(), messageText() | - |
| `BrowserTopBar.kt` | 238 | The browser's app bar: title, up, the favourite star, and the search / sort / overflow actions. BrowserTopBar(), SEARCH_FIELD_WIDTH | `BrowserTopBarTest` (4) |
| `BrowserUiState.kt` | 391 | One immutable description of everything the browser shows. ViewMode, SearchScope, Clipboard, CompressRequest, PickerFolder, TransferPicker, MessageAction, UiMessage, ConflictPrompt, RootItem, Bookmark, DetailFocus, Undo, UndoMove, Overlay, BrowserUiState | - |
| `BrowserViewModel.kt` | 2540 | Drives the browser screen. BrowserViewModel, ArchivePreview | `BrowserViewModelTest` (113) |
| `BrowserViewModel.kt` | 2598 | Drives the browser screen. BrowserViewModel, ArchivePreview | `BrowserViewModelTest` (116) |
| `DetailsSheet.kt` | 109 | Name, location, size, type and modified date for one entry. DetailsSheet(), DetailLines(), DetailLine() | - |
| `EntryIntents.kt` | 58 | Wraps an entry's path in a content:// URI another app may read. contentUriFor(), viewIntent(), shareIntent() | - |
| `IncomingBar.kt` | 60 | The bar shown while another app is waiting on this one. IncomingBar() | - |
@@ -595,7 +595,7 @@ Every test name in the suite, by file: the fastest way to see whether something
- a library or the root list has nothing to star
- the star gives way to the search field
### `ui/browser/BrowserViewModelTest.kt` (113)
### `ui/browser/BrowserViewModelTest.kt` (116)
- starts on the root list
- opening a folder lists it with directories first and natural order
@@ -626,6 +626,9 @@ Every test name in the suite, by file: the fastest way to see whether something
- opening the trash keeps what is still inside the window
- emptying the trash removes what is in it
- a shortcut opens the view it names
- typing while the index is being built does not start it again
- switching library while the index is being built does not start it again
- a search is answered again when the pass finds what it was looking for
- navigating up goes to the parent then to the roots
- selection survives toggling and clears on navigation
- a file with no preview of its own is handed to the system