Follow-up to #486.
Current state
Every human-visible duration carries a popover listing the same value under the other duration profiles — except the two navbar totals, which render as plain text (DurationText) with no popover.
They still follow the user's DURATION_FORMAT profile and carry the sr-only spoken form. Only the alternate formats are missing.
Why
Each total is already wrapped in a link to its filtered session list (today_url / last_7_url), and a popover trigger is a <button>. A button nested inside a link is invalid HTML, and tests/test_html_validity.py::test_no_interactive_element_nested_in_another fails across every page that renders the navbar — 54 of them.
Note this is not a markup-shape problem any more: NavbarPlaytime in common/layout.py was an HTML f-string wrapped in Safe() and is now a proper node tree (Li/Span/A), so a duration node's Media would survive there fine. The blocker is purely the nesting rule.
Options
Popover(preface=…) — the parameter exists for exactly this case: it renders a node (here the link) as a sibling before the trigger inside the popover host, so hovering the host opens the panel while only a small <button> is tappable, and the trigger never lands inside the link. Cost: an extra glyph beside each total in a deliberately compact bar.
- Drop the links. The alternates replace them as the affordance. Cost: loses one-click navigation to the filtered list, which is arguably the more useful of the two.
- Leave as is. The alternates are available on the list pages the links lead to, one click away.
Worth deciding alongside
#584 proposes explaining the active format once per table rather than once per value. If that lands, the navbar may not need a per-value affordance at all — a single explainer somewhere in the chrome could cover both.
Where
common/layout.py — NavbarPlaytime
games/views/general.py — model_counts, which supplies the two values
common/components/domain.py — DurationText / DurationAlternates / Duration
Follow-up to #486.
Current state
Every human-visible duration carries a popover listing the same value under the other duration profiles — except the two navbar totals, which render as plain text (
DurationText) with no popover.They still follow the user's
DURATION_FORMATprofile and carry thesr-onlyspoken form. Only the alternate formats are missing.Why
Each total is already wrapped in a link to its filtered session list (
today_url/last_7_url), and a popover trigger is a<button>. A button nested inside a link is invalid HTML, andtests/test_html_validity.py::test_no_interactive_element_nested_in_anotherfails across every page that renders the navbar — 54 of them.Note this is not a markup-shape problem any more:
NavbarPlaytimeincommon/layout.pywas an HTML f-string wrapped inSafe()and is now a proper node tree (Li/Span/A), so a duration node'sMediawould survive there fine. The blocker is purely the nesting rule.Options
Popover(preface=…)— the parameter exists for exactly this case: it renders a node (here the link) as a sibling before the trigger inside the popover host, so hovering the host opens the panel while only a small<button>is tappable, and the trigger never lands inside the link. Cost: an extra glyph beside each total in a deliberately compact bar.Worth deciding alongside
#584 proposes explaining the active format once per table rather than once per value. If that lands, the navbar may not need a per-value affordance at all — a single explainer somewhere in the chrome could cover both.
Where
common/layout.py—NavbarPlaytimegames/views/general.py—model_counts, which supplies the two valuescommon/components/domain.py—DurationText/DurationAlternates/Duration