From 1322056b220393b03c5a0d06dc571546ff4858bc Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 28 May 2026 15:20:30 -0400 Subject: [PATCH] refactor(dry-F5): useTabQuery composable for ?tab= query-param routing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ArtistView and SubscriptionsView both two-way-bound a tab ref with the ?tab= query param via identical tab->URL and URL->tab watchers. Extracted useTabQuery(validTabs, defaultTab) — defaultTab accepts a string or a function (ArtistView's default is postCount-dependent), and resolve() is exposed so ArtistView can re-apply it after the artist loads. Both views drop their now-orphaned ref/useRouter imports. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/composables/useTabQuery.js | 30 ++++++++++++++++++++ frontend/src/views/ArtistView.vue | 35 ++++++------------------ frontend/src/views/SubscriptionsView.vue | 22 ++------------- 3 files changed, 40 insertions(+), 47 deletions(-) create mode 100644 frontend/src/composables/useTabQuery.js diff --git a/frontend/src/composables/useTabQuery.js b/frontend/src/composables/useTabQuery.js new file mode 100644 index 0000000..57b39ae --- /dev/null +++ b/frontend/src/composables/useTabQuery.js @@ -0,0 +1,30 @@ +import { ref, watch } from 'vue' +import { useRoute, useRouter } from 'vue-router' + +// Two-way bind a tab ref with the ?tab= query param: the initial value comes +// from the URL (when it names a valid tab) else `defaultTab` — a string, or a +// function for a state-dependent default. Tab changes push to the URL via +// router.replace (no history spam); URL changes (back/forward) flow back into +// the ref. Returns { tab, resolve } where resolve() re-applies the URL-or- +// default rule (callers re-run it when their default inputs change). +export function useTabQuery(validTabs, defaultTab) { + const route = useRoute() + const router = useRouter() + + const resolveDefault = () => + (typeof defaultTab === 'function' ? defaultTab() : defaultTab) + const resolve = () => + (validTabs.includes(route.query.tab) ? route.query.tab : resolveDefault()) + + const tab = ref(resolve()) + + watch(tab, (t) => { + if (route.query.tab === t) return + router.replace({ query: { ...route.query, tab: t } }) + }) + watch(() => route.query.tab, (q) => { + if (q && validTabs.includes(q) && tab.value !== q) tab.value = q + }) + + return { tab, resolve } +} diff --git a/frontend/src/views/ArtistView.vue b/frontend/src/views/ArtistView.vue index 876b9d8..0dcf90f 100644 --- a/frontend/src/views/ArtistView.vue +++ b/frontend/src/views/ArtistView.vue @@ -39,30 +39,26 @@