fix(series): sticky tabs + controls on the Series view
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 31s
CI / integration (push) Successful in 3m14s

The Series tab strip and the Browse search/sort (and Suggestions controls)
scrolled away on a long grid (operator-asked). Hoist the tabs + active-tab
controls into one sticky header pinned under the 64px TopNav. The controls
had to leave v-window — it clips sticky children — so they're driven by the
tab from the header instead of living inside each window-item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 20:30:27 -04:00
parent 5c3f8ebd70
commit 7fcef53d5b
+61 -46
View File
@@ -1,35 +1,60 @@
<template>
<v-container fluid class="pt-2 pb-6">
<v-tabs v-model="tab" density="compact" class="mb-3">
<v-tab value="browse">Browse</v-tab>
<v-tab value="suggestions">
Suggestions
<v-badge
v-if="sug.suggestions.length" :content="sug.suggestions.length"
inline color="accent"
<!-- Tabs + the active tab's controls live in one sticky block pinned under
the 64px TopNav (operator-asked 2026-06-12), so the axis switcher and
search/sort stay reachable on a long grid. The controls can't sit
inside v-window (it clips sticky children), so they're hoisted here. -->
<div class="fc-series__head">
<v-tabs v-model="tab" density="compact">
<v-tab value="browse">Browse</v-tab>
<v-tab value="suggestions">
Suggestions
<v-badge
v-if="sug.suggestions.length" :content="sug.suggestions.length"
inline color="accent"
/>
</v-tab>
</v-tabs>
<div v-if="tab === 'browse'" class="fc-series-browse__controls">
<v-text-field
v-model="query"
density="compact" variant="outlined" hide-details clearable
label="Search series" prepend-inner-icon="mdi-magnify"
style="max-width: 320px;"
/>
</v-tab>
</v-tabs>
<v-select
:model-value="store.sort"
:items="sortItems"
density="compact" variant="outlined" hide-details
label="Sort" style="max-width: 220px;"
@update:model-value="store.setSort($event)"
/>
</div>
<div v-else class="fc-sug__controls">
<v-switch
:model-value="sug.enabled" color="accent" density="compact"
hide-details label="Matching on"
@update:model-value="sug.setEnabled($event)"
/>
<v-text-field
:model-value="sug.threshold"
type="number" min="0" max="1" step="0.05"
density="compact" variant="outlined" hide-details
label="Threshold" style="max-width: 130px;"
@change="sug.setThreshold(Number($event.target.value))"
/>
<v-spacer />
<v-btn
variant="tonal" prepend-icon="mdi-radar" @click="sug.rescan()"
>Rescan</v-btn>
</div>
</div>
<v-window v-model="tab">
<!-- ---- Browse ---- -->
<v-window-item value="browse">
<div class="fc-series-browse__controls">
<v-text-field
v-model="query"
density="compact" variant="outlined" hide-details clearable
label="Search series" prepend-inner-icon="mdi-magnify"
style="max-width: 320px;"
/>
<v-select
:model-value="store.sort"
:items="sortItems"
density="compact" variant="outlined" hide-details
label="Sort" style="max-width: 220px;"
@update:model-value="store.setSort($event)"
/>
</div>
<v-alert v-if="store.error" type="error" variant="tonal" closable class="my-4">
Failed to load: {{ store.error }}
</v-alert>
@@ -138,25 +163,6 @@
<!-- ---- Suggestions ---- -->
<v-window-item value="suggestions">
<div class="fc-sug__controls">
<v-switch
:model-value="sug.enabled" color="accent" density="compact"
hide-details label="Matching on"
@update:model-value="sug.setEnabled($event)"
/>
<v-text-field
:model-value="sug.threshold"
type="number" min="0" max="1" step="0.05"
density="compact" variant="outlined" hide-details
label="Threshold" style="max-width: 130px;"
@change="sug.setThreshold(Number($event.target.value))"
/>
<v-spacer />
<v-btn
variant="tonal" prepend-icon="mdi-radar" @click="sug.rescan()"
>Rescan</v-btn>
</div>
<v-alert v-if="sug.error" type="error" variant="tonal" closable class="my-4">
{{ sug.error }}
</v-alert>
@@ -287,8 +293,17 @@ onMounted(() => {
</script>
<style scoped>
/* Sticky header (tabs + active-tab controls) pinned under the 64px TopNav, so
content scrolls cleanly beneath it. Surface bg matches SettingsView. */
.fc-series__head {
position: sticky;
top: 64px;
z-index: 4;
background: rgb(var(--v-theme-surface));
padding-bottom: 12px;
}
.fc-series-browse__controls {
display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 16px;
display: flex; flex-wrap: wrap; gap: 12px; padding-top: 12px;
}
.fc-series-browse__empty {
padding: 48px; text-align: center;
@@ -351,7 +366,7 @@ onMounted(() => {
/* Suggestions tab */
.fc-sug__controls {
display: flex; align-items: center; gap: 16px; margin-bottom: 12px;
display: flex; align-items: center; gap: 16px; padding-top: 12px;
}
.fc-sug__list { display: flex; flex-direction: column; gap: 8px; }
.fc-sug__row {