fix(ui): measure real nav height (--fc-nav-h), stop Explore breadcrumb tucking under nav (#1481)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 30s
CI / integration (push) Successful in 3m44s

The app uses a plain sticky TopNav (no v-main), and the nav's height was
hardcoded as 64px in ~6 places: the Explore + Subscriptions full-height
workspaces (height: calc(100vh - 64px)) and every sticky sub-header pinned
beneath the nav (top: 64px — Gallery filter bar, Browse/Series/Settings tabs).

Vuetify 4's MD3 sizing changed the real nav height, so 64px was wrong: the
Explore workspace was sized taller than the space below the nav, overflowed the
viewport, and its breadcrumb tucked under the (taller) nav on 1080p.

TopNav now measures its own height via ResizeObserver and publishes it as
--fc-nav-h on documentElement (default 64px in app.css). Every consumer uses
var(--fc-nav-h) instead of the magic number, so the layout self-corrects to the
nav's real height and stays correct as it reflows (per-view teleported actions,
mobile breakpoint). Also tightens the new chrome-gradient seam — sub-headers now
pin at the nav's exact bottom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-13 15:27:37 -04:00
parent eed42a260a
commit 67c7ca8603
8 changed files with 39 additions and 11 deletions
+21 -3
View File
@@ -1,5 +1,5 @@
<template>
<header class="fc-topnav" :class="{ 'fc-topnav--chrome': hasStickyChrome }">
<header ref="navEl" class="fc-topnav" :class="{ 'fc-topnav--chrome': hasStickyChrome }">
<div class="fc-nav-left">
<RouterLink :to="FRONT_DOOR" class="fc-brand" aria-label="FabledCurator home">
<img src="/favicon.svg" alt="" class="fc-brand__glyph" width="22" height="22" />
@@ -64,14 +64,32 @@
</template>
<script setup>
import { computed, onMounted } from 'vue'
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import router, { FRONT_DOOR } from '../router.js'
import { useSystemStore } from '../stores/system.js'
import PipelineStatusChip from './PipelineStatusChip.vue'
const system = useSystemStore()
onMounted(() => system.refreshHealth())
// Publish the nav's REAL height as --fc-nav-h so full-height workspaces
// (Explore/Subscriptions) and sticky sub-headers pin to it exactly instead of a
// hardcoded 64px that Vuetify 4's MD3 sizing broke — the Explore breadcrumb was
// tucking under a taller nav (#1481). ResizeObserver keeps it live as the nav
// reflows (per-view teleported actions, mobile breakpoint, chip state changes).
const navEl = ref(null)
let navRO = null
onMounted(() => {
system.refreshHealth()
if (navEl.value && 'ResizeObserver' in window) {
navRO = new ResizeObserver(() => {
const h = navEl.value?.offsetHeight
if (h) document.documentElement.style.setProperty('--fc-nav-h', `${h}px`)
})
navRO.observe(navEl.value)
}
})
onBeforeUnmount(() => { navRO?.disconnect() })
// Views that pin a sticky sub-header (filter bar / tabs) directly under the nav
// declare `meta.stickyChrome`. On those, the nav doesn't fade to transparent at