0.3.27 - Omni search fixes, Services in genre browser..
This commit is contained in:
@@ -703,11 +703,76 @@ of a copy, wrong the moment Tracearr corrects a session. It is read two ways:
|
||||
6×6 on-screen keyboard on the left, with `SearchGenres` above `SearchResults` on the right.
|
||||
The results list updates as you type and uses the same shared `MediaResultCard` presentation
|
||||
as My Requests; request status and actions remain feature-owned slots around that shared row.
|
||||
Nothing is ever "submitted". `SearchViewModel` runs one pipeline — `debounce(250)` →
|
||||
`trim` → `distinctUntilChanged` → `collectLatest { repository.search(it) }` — and
|
||||
`collectLatest` is the load-bearing part: it cancels the in-flight request, so a slow
|
||||
response for a prefix can never overwrite the results for what was typed after it.
|
||||
Searching starts at two characters (`shouldSearch`); one letter matches half a library.
|
||||
**It is two pipelines, and the whole design is that they never wait for each other.**
|
||||
`SearchViewModel` runs a *library* search on `debounce(250)` → `trim` →
|
||||
`distinctUntilChanged` → `collectLatest { repository.search(it) }`, where `collectLatest`
|
||||
is the load-bearing part: it cancels the in-flight request, so a slow response for a prefix
|
||||
can never overwrite the results for what was typed after it. Searching starts at two
|
||||
characters (`shouldSearch`); one letter matches half a library. Beside it runs *external
|
||||
discovery* — `GET /v1/search/discover`, Sonarr and Radarr — which used to ride the same
|
||||
per-keystroke stream and therefore sent nine lookups to two of somebody else's services on
|
||||
the way to typing one title. Things to preserve:
|
||||
|
||||
- **Discovery is deliberately hard to trigger, in four independent ways.** A longer
|
||||
debounce (`DISCOVERY_DEBOUNCE_MS`, 700ms, several times the library's — at 250ms most of
|
||||
the gaps between presses on a remote look like the end of a word), a three-character
|
||||
floor (`MIN_DISCOVERY_QUERY_LENGTH`, where the library's is two), a `distinctUntilChanged`
|
||||
over the **normalised** query, and a session cache. Any one of them removed puts the
|
||||
*arrs back on the keystroke path.
|
||||
- **`normaliseDiscoveryQuery` exists twice** — `data/SearchDiscovery.kt` and the gateway's
|
||||
`normaliseDiscoveryQuery` — and the two must agree, because it is what the server's
|
||||
cooldown is keyed on: "Disclosure", "disclosure" and "disclosure " have to reach the
|
||||
cache rather than Radarr.
|
||||
- **Pressing Search, and finishing a voice query, skip the debounce.** They are somebody
|
||||
saying they have finished typing, which is the one thing a timer is guessing at — so the
|
||||
keyboard's Search key and `onVoiceResult` both go through `submitQuery`, which runs the
|
||||
library search immediately and emits into `submissions`, the flow merged past the
|
||||
debounce. A completed transcription is a submitted query, never a keystroke.
|
||||
- **The cooldown is the server's, keyed on `cache.MetadataKey`.** What Radarr knows about a
|
||||
title is a fact about the world and the household's catalogue with nothing derived from
|
||||
whoever typed it, so one lookup answers every television for `discoveryCacheTTL` (10
|
||||
minutes; `discoveryEmptyTTL`, 2, for a lookup that found nothing, because nothing is the
|
||||
answer for a typo and typos get corrected). `cachedRead` puts a singleflight in front of
|
||||
it, so two sets missing the same key in the same second make one lookup. Whether *this*
|
||||
viewer may press Request is stamped on the way out (`applyRequestable`) rather than baked
|
||||
into what the household shares — the `MetadataKey` rule, exactly.
|
||||
- **`state.discovery` is kept apart from `state.results`.** That separation is what makes
|
||||
every promise below it true: the library list cannot be reordered by a late answer,
|
||||
nothing on screen moves when the second section arrives, and it is simply appended under
|
||||
whatever the viewer is already looking at. Merging the two lists is how a focused card
|
||||
comes to jump.
|
||||
- **A title is never offered twice.** The gateway drops anything its own catalogue answers
|
||||
for (`discoverSonarr` / `discoverRadarr` skip `inLibrary`); `discoverySection` on the
|
||||
television is the other half, for a title Emby holds that the import has not caught up
|
||||
with. Both match on a stable id first (`searchIdentity`: Tmdb, Tvdb, Imdb) and fall back
|
||||
to title-and-type, which is deliberately the weakest rule rather than the first tried.
|
||||
`/v1/search` asks Emby for `ProviderIds` for exactly this — hence the `search:v2:` cache
|
||||
key.
|
||||
- **Sonarr or Radarr being down is never an error the viewer sees.** A failed leg leaves
|
||||
the library section reading exactly as it does when everything works, and the section
|
||||
says "Additional results unavailable" or says nothing. `partial` on the response is what
|
||||
distinguishes "nothing else exists" from "we could not ask"; only the second is worth a
|
||||
line.
|
||||
- **A requestable result opens a page, and does not send a request.** `ui/
|
||||
RequestDetailsOverlay.kt` is that page — the same backdrop, poster, fact line and button
|
||||
language every other detail page has, deliberately *not* the ordinary movie or series
|
||||
layout, which is built around a file that exists and would carry Play, a resume bar and
|
||||
watched state as four lies arranged as furniture. It is the judgement
|
||||
`RadarrMovieDetailsOverlay` already makes for the schedule row, and the two are built
|
||||
from the same pieces. It is stateless — poster and message are parameters — so
|
||||
`SearchSectionsScreenshotTest` renders it with no gateway.
|
||||
- **It replaced the request *mode*.** Search used to carry a Request chip in its heading
|
||||
that swapped the results pane for a grid of `lookupMediaRequests` candidates. Discovery
|
||||
is that feature, inline and progressive, and one thing reachable two ways is one thing
|
||||
whose two entry points drift apart.
|
||||
- **`ui/search/SearchTrace.kt` is where the time went** (`adb logcat -s MembySearch`),
|
||||
debug-only like `StartupTrace`. The most useful line is `event=discovery_skipped`: an
|
||||
external lookup that never happened is the feature *working*, and without a record of why
|
||||
it is indistinguishable from one that silently failed. It logs the query's length rather
|
||||
than the query.
|
||||
- **The gateway's own line is DEBUG**, for the reason the search line is, and carries the
|
||||
per-source counts, durations and cache verdicts plus how many duplicates the merge
|
||||
removed.
|
||||
`rankSearchResults` is a pure, stable sort that only lifts exact/prefix/word-boundary
|
||||
title matches above the backend's own relevance order — it never re-sorts alphabetically,
|
||||
and it keeps weak matches rather than showing an empty pane. A small access-ordered map
|
||||
|
||||
Reference in New Issue
Block a user