feat(auto): page local-music folders instead of truncating at 50 [size:exception]

Folders over the 50-item cap now show a "Mas..." item that reveals
the next page on tap, instead of silently dropping the rest. Paging
slices the cheap raw list before building any MediaItem, so items
beyond the requested page are never resolved (art, title) -- proven
by a call-count test. Also swaps the raw SAF content:// URI shown in
settings for a parsed, human-readable folder name with a localized
fallback across all 13 locales.

servicio_audio.dart is untouched; this stays entirely within the
local-music tree/dispatch layer.
This commit is contained in:
2026-07-19 22:14:05 +02:00
parent 977cbcd8cc
commit 725169cd31
25 changed files with 1601 additions and 40 deletions
@@ -0,0 +1,70 @@
# Verification Report: android-auto-local-music-paging
Mode: hybrid (Engram + openspec file)
Verdict: PASS WITH WARNINGS
## Priority Investigation - l10n generated-file staleness
Question: Is the stale lib/l10n/gen/app_localizations.dart a ship-blocking compile failure, or a sandbox-only false alarm?
Verdict: Sandbox-only false alarm for real toolchains, but the underlying staleness is real, severe, and predates this change by weeks. Not a CRITICAL / ship-blocking finding. Flagged as WARNING (process hygiene).
Evidence:
1. lib/l10n/gen/app_localizations.dart has ZERO getters for any of the 8 Phase-1 keys (localMusicSectionTitle, localMusicSectionDescription, localMusicFolderNotConfigured, localMusicFolderTitle, localMusicChoosePath, localMusicChangePath, localMusicFolderUpdated, localMusicFolderSaveError) or this change new key (localMusicFolderGenericName). rg returned no hits in that file for any of the 9 keys.
2. The file IS tracked in git (git log -1 -- lib/l10n/gen/app_localizations.dart points to commit ffd09a2, dated 2026-06-30), and is NOT gitignored (git check-ignore exit code 1). It is genuinely stale, not a build artifact excluded from history.
3. lib/l10n/app_es.arb (the l10n.yaml template-arb-file) has 499 keys; the generated app_localizations.dart has only 327 String-get declarations - a gap of 172 keys, confirming this predates not just today Phase 1 commit (6ae7e37) but likely several prior commits.
4. pubspec.yaml sets flutter: generate: true (line 65), and l10n.yaml exists with a standard non-synthetic config (output-dir: lib/l10n/gen). Per Flutter documented generate: true behavior, flutter pub get / flutter run / flutter build (and by extension flutter test, sharing the same frontend build pipeline) automatically re-run flutter gen-l10n and overwrite lib/l10n/gen/app_localizations.dart from current .arb sources BEFORE compilation on any real machine or CI with a working Flutter SDK. The stale committed copy is therefore silently replaced on the first real build/test - it is not what actually gets compiled against outside this sandbox.
5. Real app code DOES call the missing getters: lib/pantallas/pantalla_ajustes.dart lines 320, 325, 343, 350, 360, 363, 366, 385-386 call l10n.localMusicFolderUpdated, l10n.localMusicFolderSaveError, l10n.localMusicSectionTitle, l10n.localMusicSectionDescription, l10n.localMusicFolderTitle, l10n.localMusicFolderNotConfigured, l10n.localMusicFolderGenericName, l10n.localMusicChangePath, l10n.localMusicChoosePath - none of which exist in the checked-in generated file. This DOES mean the checked-in copy, if compiled as-is, would fail. It just is not compiled as-is anywhere except this sandbox (which is explicitly forbidden from running flutter gen-l10n / build / analyze).
6. There is no .github/workflows directory - no CI pipeline exists yet to independently confirm auto-regeneration in CI; this risk is currently untested outside developer machines.
Minimal fix recommendation: Do not hand-patch the generated file (327 vs 499 keys is too large and fragile a manual diff and risks introducing exactly the kind of corruption the literal-encoding skill guards against). Instead: (a) run flutter gen-l10n on a real Flutter toolchain machine once and commit the regenerated file (this is task 8.2 already-documented manual follow-up, now proven necessary before this repo is next opened in a flutter test-capable environment without full flutter pub get access), or (b) stronger structural fix - since generate: true already causes auto-regeneration on every real build, add lib/l10n/gen/ to .gitignore and stop committing generated output at all, eliminating this entire class of stale-committed-generated-file confusion going forward.
## Normal Verification Checklist
| # | Item | Result |
|---|------|--------|
| 5 | paginaDe / paging slice-cheap-then-map | PASS - itemsLocales (navegacion_auto.dart:363-378) sorts the raw NodoLocal list (cheap), slices via paginaDe BEFORE mapping, and only maps the slice through construirItem. Test "invariante de conteo de llamadas (ADR-3)" (navegacion_auto_test.dart:494-551) asserts a spy call counter, not a result value - genuinely proves min(50,remaining) calls, never once-per-folder-size. |
| 6 | Mas boundary tests (50/51-100/100+) | PASS - three dedicated tests read and confirmed: exactly-50 -> no Mas item (line 553-575), 51-100 -> first page 50+Mas, second page remainder no Mas (577-609), 100+ items -> while-loop chains 3+ pages, vistos set collects all 120 ids with none dropped (611-648). |
| 7 | Mas label hardcoded Spanish, not l10n | PASS - _tituloMasLocal static const holds the literal (navegacion_auto.dart:331); zero arb files contain a Mas-item key (grep -rn empty result). |
| 8 | servicio_audio.dart untouched | PASS - git diff --stat -- lib/servicios/servicio_audio.dart returns no output (zero changes). |
| 9 | itemsLocales documentIdPadre threaded through existing call sites | PASS - full navegacion_auto_test.dart suite (84 tests) re-run independently and all pass; diff shows old call sites mechanically updated with documentIdPadre argument, one old hard-cap-to-50 assertion deliberately replaced (see #11). |
| 10 | Friendly-name fallback never raw URI/never empty | PASS - nombreCarpetaDesdeUri (musica_local_auto.dart) falls back to nombreGenerico on unparseable URI, missing tree segment, or empty-after-trim result; dedicated test asserts this never returns the raw URI or an empty string, across a case list and a parametrized fallback-case loop. |
| 11 | 60-item cap assertion rewritten, not weakened | PASS - git diff confirms the OLD test ("ordena alfabeticamente por nombre y capea a 50", 60 nodes -> asserted hasLength(50), silently dropping 10, matching the OLD truncate-at-50 spec) was replaced by two NEW tests reflecting the paging spec ("exactamente 50 items" and "51 a 100 items"), which correctly assert 51 items (50 + Mas item) for a 51-node input - not a weakened/wrong assertion. |
| 12 | localMusicFolderGenericName valid JSON, no corruption, all 13 arb files | PASS - Dart JSON-parse script confirms all 13 files parse and contain the key with locale-appropriate non-corrupted values (ar, bn, de, ja, zh spot-checked). Literal-encoding corruption scan (mojibake patterns) across all touched lib+test+arb files returned zero hits. |
| 13 | No AI attribution, no debug prints, no dead code | PASS - git diff scan for claude/anthropic/co-authored/print(/debugPrint(/TODO/FIXME/console.log across all touched lib/ and test/ files returned zero hits. |
| 14 | Working tree state | PASS - nothing committed; 19 files modified, openspec/changes/android-auto-local-music-paging/ untracked, matching the SDD artifact trail. No stray commits. |
| 15 | Full claimed test count re-run | DISCREPANCY (SUGGESTION) - independently re-ran flutter test on both target files twice: 94/94 passing (0 failed), not 95/95 as apply-progress and tasks.md 8.1 claim. navegacion_auto_test.dart has 84 test() calls (not 85 as claimed); musica_local_auto_test.dart has 10 (matches). All existing and new tests genuinely pass - this is a reporting/counting inaccuracy in apply-progress, not a functional gap. |
| 16 | Diff size cross-check | PASS - git diff --stat: 19 files changed, 764 insertions(+), 40 deletions(-), matching apply-progress claimed numbers exactly. |
## Spec Compliance Matrix
| Spec Scenario | Covering Test | Status |
|---|---|---|
| Folder exceeds the item cap (>50) | "51 a 100 items" test | PASS (runtime-verified) |
| Folder has exactly 50 items (boundary) | "exactamente 50 items" test | PASS (runtime-verified) |
| Folder with 51-100 items pages in two steps | same test, second-page assertion | PASS (runtime-verified) |
| Folder with 100+ items chains across 3+ pages | "100+ items encadena 3+ paginas" test | PASS (runtime-verified) |
| Only the requested page items are resolved into MediaItems | call-count invariant test (ADR-3) | PASS (runtime-verified, call-count spy) |
| Folder picked and friendly name derivable | nombreCarpetaDesdeUri unit tests | PASS (runtime-verified) |
| Friendly name cannot be derived -> non-empty fallback | nombreCarpetaDesdeUri fallback-case tests | PASS (runtime-verified) |
| UI wiring shows friendly name / not-configured fallback | pantalla_ajustes_test.dart Phase 7 widget tests (7.1-A, 7.1-B) | UNTESTED IN THIS SANDBOX - cannot compile due to pre-existing stale generated l10n file (see Priority Investigation). Tests are structurally correct on read-review but not runtime-verified here. |
## Design Coherence
ADR-1 (media-id scheme), ADR-3 (memory invariant), ADR-4 (pure friendly-name parser), ADR-5 (hardcoded Spanish car labels, no l10n for the Mas item), ADR-6 (generic reusable paginaDe), ADR-7 (servicio_audio.dart untouched) - all confirmed against live code, no deviations found.
## Issues by Severity
### CRITICAL
None.
### WARNING
1. lib/l10n/gen/app_localizations.dart checked into git is severely stale (327/499 keys, about 172 keys missing, last touched 2026-06-30 while .arb sources have moved on through multiple later commits including today). Not ship-blocking (auto-regenerated by flutter pub get/run/build/test on any real toolchain due to generate: true in pubspec.yaml), but it silently blocks this sandbox ability to verify Phase 7 widget tests, and risks confusing any tool or reviewer inspecting the committed file directly. Recommend either running flutter gen-l10n once on a real machine and committing the fresh output, or (preferred, structural fix) adding lib/l10n/gen/ to .gitignore so this class of drift becomes structurally impossible.
2. test/pantallas/pantalla_ajustes_test.dart Phase 7 tests (7.1-A, 7.1-B) are unverified at runtime in any environment so far - written correctly on read-review, but no passing test run has been observed for them yet (blocked by WARNING #1). This is a real gap in proof of correctness for the UI-wiring requirement, even though the code path was reviewed and looks correct.
### SUGGESTION
1. Test-count reporting discrepancy: apply-progress and tasks.md (task 8.1) claim 95/95 and 85/85 for navegacion_auto_test.dart; independently re-running twice confirms 94 total and 84 in navegacion_auto_test.dart. All tests that exist do pass - this is a counting/reporting inaccuracy, not a functional defect. Recommend correcting the record before archive.
## Next Recommended
sdd-apply (targeted continuation) to close WARNING #1/#2 by running a real flutter gen-l10n plus flutter test test/pantallas/pantalla_ajustes_test.dart pass outside this sandbox, then re-verify Phase 7 specifically. Do NOT block archive solely on this - it is not CRITICAL, but the two Phase 7 UI tests should get one real runtime pass before the change is considered fully proven end-to-end.