# Station Discovery & Browse Specification ## Purpose Discovery content as the Buscar tab's landing state, active-filter pills, results counter, clear-all-filters, the Países browser over the verified `/json/countries` contract, and a client-side search-sort control. Traceable to WU5 (relocation), WU6 (Buscar), WU7 (Países + `_get` transport extraction). ## Requirements ### Requirement: Buscar Landing State Shows Discovery Content Buscar MUST render the existing discovery content (near-you, genres, trending, countries entry point) as its landing state whenever no search query is entered. Entering a query MUST replace that landing state with search results. #### Scenario: Empty query shows discovery content - GIVEN the user opens the Buscar tab with no prior query - WHEN the screen renders - THEN near-you, genre, trending, and a Países entry point are visible #### Scenario: Entering a query replaces the landing state - GIVEN the Buscar tab is showing discovery content - WHEN the user types a non-empty query - THEN discovery content is replaced by the search-results view ### Requirement: Active-Filter Pills and Results Counter The search-results view MUST show each active filter (country, language, minimum quality) as a removable pill, and MUST show a results counter ("N RESULTADOS"). #### Scenario: Applying a filter shows a removable pill - GIVEN the user has an active search - WHEN they apply a country filter - THEN a pill labeled with that country and a close (x) affordance appears - AND the results counter reflects the filtered count #### Scenario: Removing a pill re-runs the search without that filter - GIVEN a country filter pill is active - WHEN the user taps its close affordance - THEN the filter is cleared and results/count update accordingly ### Requirement: One-Tap Clear-All-Filters on Empty Results When 1 or more filters are active and the resulting search has zero results, the system MUST offer a single action that clears every active filter at once ("Quitar los N filtros"). #### Scenario: Two active filters, zero results - GIVEN country and quality filters are both active and yield 0 results - WHEN the empty state renders - THEN a "Quitar los 2 filtros" action is shown - AND tapping it clears both filters in one action, not one at a time ### Requirement: Países Browser Over the Verified Countries Contract The Países screen (new second-level screen) MUST fetch from **`/json/countries`** (optional substring filter `/json/countries/{filter}`) and MUST NOT use `/json/countrycodes`, which is deprecated. It MUST parse the `stationcount` field as a **string** via `int.tryParse`, never as a direct `int` cast. Its request MUST NOT include `lastcheckok`, since that parameter is meaningful only for station-listing calls. Source: Engram `reference/radio-browser-countries-endpoint` (id 2500), verified against . #### Scenario: Countries list renders with counts - GIVEN `/json/countries` returns a list of country objects - WHEN the Países screen loads - THEN "Tus idiomas" and the full alphabetical list render with each country's parsed station count #### Scenario: `stationcount` arrives as a JSON string (edge case, critical) - GIVEN a country entry has `"stationcount": "482"` (a JSON string, not a number) - WHEN the screen parses and renders that entry - THEN it displays `482` without throwing a type-cast error #### Scenario: Countries request omits the station-only filter - GIVEN the Países screen issues its `/json/countries` request - WHEN the outgoing request is inspected - THEN it does NOT include `lastcheckok` ### Requirement: Client-Side Search Sort Only Buscar MUST expose an "Ordenar" control backed exclusively by client-side ordering (`OrdenEmisoras`). The system MUST NOT adopt the Radio Browser server-side `order`/`reverse` parameters for this control, because server-side ordering sorts the full result set before `limit` is applied while the app's client-side sort only reorders the page already fetched — adopting it would silently change which stations users see, not just their order. Every sort option rendered in the UI MUST map to a real `OrdenEmisoras` case; the system MUST NOT render an option that does not actually sort. Source: Engram `reference/radio-browser-sort-order` (id 2505), verified against . #### Scenario: Selecting a sort option reorders the current page only - GIVEN a set of search results is already fetched - WHEN the user selects "Ordenar: calidad" - THEN the currently-displayed page is reordered via `OrdenEmisoras.calidad` - AND no new network request with an `order` parameter is issued #### Scenario: Every rendered sort option is backed by a real case (regression guard) - GIVEN the "Ordenar" control's rendered option list - WHEN each option is checked against `OrdenEmisoras` - THEN every option maps to an existing enum case with test coverage; no decorative option that does not sort is rendered ### Requirement: Existing Station Calls Unchanged by Transport Extraction Extracting `_get`'s transport loop (server discovery, host rotation, bounded retries, User-Agent, timeout, status check, `json.decode`) into a shared helper for the Países call MUST NOT change the observable behavior of the 8 existing `ServicioRadio` station methods: `obtenerPopulares`, `obtenerTendencias`, `buscarPorNombre`, `buscarPorPais`, `buscarPorIdioma`, `buscarPorTag`, `buscar`, `registrarClick`. #### Scenario: Characterization tests pass unchanged before and after extraction - GIVEN characterization tests exist for all 8 station methods - WHEN the transport loop is extracted from `_get` - THEN all 8 tests pass identically before and after the extraction #### Scenario: Station calls still send `lastcheckok` - GIVEN any of the 8 station methods issues a request - WHEN the outgoing request is inspected - THEN it still includes `lastcheckok=1`, unchanged from before the extraction