From bbd31c7143359064f1e079057485240a4cb8bbac Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sun, 13 Sep 2026 07:36:41 +0200 Subject: [PATCH] feat(keys): Back returns focus to the item a view was opened from; "Feed" everywhere; every backend id named MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Owner answers to the E11 S5 raises (2026-09-13): 1 → A, 2 → "Feed" in every language, 3 → translate the proxy label and fix every config key missing in BOTH languages ("maintenance", "tmdb" — old bugs). Focus across a view change (1-A). Opening a Plex title from Home's rail, a channel from the Channels table or a title from a Plex grid switches the page or the sub-view, which unmounts the list; Back used to hand focus to , so the next `j` started over at the top. The YouTube player (an overlay) already restored onto the card. - `lib/listNav`: `navPosition()` (list's place in reading order, the page's list count, item index) and `restoreNavPosition()`: focuses the item once the page has as many lists on screen as when it was left, polls on a timer (a hidden tab runs no animation frames), gives up after 2 s or at once when something already holds focus, focuses without scrolling (the scroll restore owns the position). - `lib/history`: `rememberNavPosition()` stamps `sfNav` onto the entry being left and returns the state without it (a new entry must not inherit it); one popstate listener restores and spends the stamp, so a later landing on the same entry (an overlay closing over it) cannot pull focus to an old item. - Called before every view push: `setPage`, `openChannel`, `enterYtSearch`, `useHistorySubview.open`, the Channels tab push. `navPosition.wiring.contract.test` fails on any `history.pushState(` without it (the overlay stack's own push is the one exemption). "Feed" (2). The module is "Feed" in both languages: every "hírfolyam" in the Hungarian UI (≈45 strings, suffixed: Feedből, Feeded, Feedben…) and the prose "feed" in English that means the module ("your Feed", "Hide from Feed"); "RSS feed" keeps its meaning. The welcome email says it too. Backend ids without a name (3). The Configuration page showed `maintenance`, `tmdb`, `maintenance_revalidate_batch`, `download_upload_max_bytes`, `audit_retention_days`, `plex_overlay_map`, `tmdb_api_key` and `tmdb_language` as raw identifiers — the lookup falls back to the id, and the i18n parity test stayed green because both languages missed the same keys. Added labels + hints (EN/HU), the two groups (and their tab order), the missing `scheduler.jobDesc.demo_reset`, removed the stale `plex_sync_interval_min` field, and "YouTube API kimenő proxy". `i18n/backendIds.contract.test` reads `sysconfig.SPECS`, `scheduler.JOB_INTERVALS` and the audit/quota action enums from the backend sources and fails on an id without its name in either language, or a name left for an id the backend dropped. --- CLAUDE.md | 1 + backend/app/mailtext.py | 4 +- frontend/src/components/ChannelsProvider.tsx | 2 + frontend/src/components/ConfigPanel.tsx | 2 + .../src/components/NavigationProvider.tsx | 28 +++-- frontend/src/i18n/backendIds.contract.test.ts | 103 +++++++++++++++++ frontend/src/i18n/locales/en/channel.json | 8 +- frontend/src/i18n/locales/en/channels.json | 26 ++--- frontend/src/i18n/locales/en/config.json | 30 ++++- frontend/src/i18n/locales/en/feed.json | 14 +-- frontend/src/i18n/locales/en/login.json | 2 +- .../src/i18n/locales/en/notifications.json | 2 +- frontend/src/i18n/locales/en/onboarding.json | 6 +- frontend/src/i18n/locales/en/playlists.json | 2 +- frontend/src/i18n/locales/en/plex.json | 2 +- frontend/src/i18n/locales/en/scheduler.json | 7 +- frontend/src/i18n/locales/en/settings.json | 8 +- frontend/src/i18n/locales/en/stats.json | 2 +- frontend/src/i18n/locales/en/welcome.json | 10 +- frontend/src/i18n/locales/hu/channel.json | 8 +- frontend/src/i18n/locales/hu/channels.json | 28 ++--- frontend/src/i18n/locales/hu/config.json | 30 ++++- frontend/src/i18n/locales/hu/feed.json | 14 +-- frontend/src/i18n/locales/hu/header.json | 6 +- frontend/src/i18n/locales/hu/keys.json | 2 +- frontend/src/i18n/locales/hu/login.json | 2 +- .../src/i18n/locales/hu/notifications.json | 2 +- frontend/src/i18n/locales/hu/onboarding.json | 6 +- frontend/src/i18n/locales/hu/playlists.json | 2 +- frontend/src/i18n/locales/hu/plex.json | 2 +- frontend/src/i18n/locales/hu/scheduler.json | 7 +- frontend/src/i18n/locales/hu/settings.json | 8 +- frontend/src/i18n/locales/hu/stats.json | 2 +- frontend/src/i18n/locales/hu/welcome.json | 10 +- .../src/lib/history.navPosition.dom.test.tsx | 106 ++++++++++++++++++ frontend/src/lib/history.ts | 33 +++++- frontend/src/lib/listNav.ts | 51 ++++++++- .../lib/navPosition.wiring.contract.test.ts | 54 +++++++++ 38 files changed, 519 insertions(+), 113 deletions(-) create mode 100644 frontend/src/i18n/backendIds.contract.test.ts create mode 100644 frontend/src/lib/history.navPosition.dom.test.tsx create mode 100644 frontend/src/lib/navPosition.wiring.contract.test.ts diff --git a/CLAUDE.md b/CLAUDE.md index 42c11893..c273a7e8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -91,6 +91,7 @@ CQRS, a repository/DI framework, a router library, a generic CRUD-router factory | FE action button | `ui/Button` — pick a `SIZES` rung | a hand-rolled `