From 8b678c06261a905c44f5e9997611bb718bf5e78f Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Sat, 9 May 2026 22:49:33 +0200 Subject: [PATCH] fix(timetable): eliminate ?day URL flicker by deriving isFlatEvent from event_type instead of subEvents length (B7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase A finding A5 traced this race in the browser logs: GET .../performances?day={festival_id} → 200, 0 results ← wrong day GET .../children → 200, 3 sub_events GET .../performances?day={subevent_id} → 200, 13 results ← correct The pre-fix `isFlatEvent` was: computed(() => !subEvents.value || subEvents.value.length === 0) While `subEvents` was still loading (undefined), `!undefined` is `true`, so isFlatEvent erroneously returned `true` for festivals during the loading window. dayOptions then took the flat-event branch and seeded validSubEventIds with the FESTIVAL id. useActiveDay's corrective watcher rewrote the URL to `?day={festival_id}` and fired a wasted query that returned zero results (correct semantics — performances live at sub-event level — but waste + visible URL flicker). Fix: computed(() => eventDetail.value?.event_type === 'event') EventResource always serialises event_type (verified at api/app/Http/Resources/Api/V1/EventResource.php:26). EventTabsNav already consumes event_type / is_festival from the same shape (apps/app/src/components/events/EventTabsNav.vue:175,266) so this is the canonical signal, not a one-off addition. New behavior trace: - Both queries pending → eventDetail=undefined → isFlatEvent=false → festival branch returns (subEvents ?? []).map(...) → validSubEventIds=[] → activeDayId=null → usePerformances.enabled=false → NO fetch - subEvents resolves first → festival branch populates dayOptions → fetch fires with correct sub-event id - eventDetail resolves first to flat event → flat branch fires → fetch with eventDetail.id (correct) - eventDetail resolves first to festival → still false until subEvents → no false-positive flat-event fetch 402 tests still pass; typecheck + lint + production build all green. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/app/src/pages/events/[id]/timetable/index.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/app/src/pages/events/[id]/timetable/index.vue b/apps/app/src/pages/events/[id]/timetable/index.vue index 246245c5..79d1d70d 100644 --- a/apps/app/src/pages/events/[id]/timetable/index.vue +++ b/apps/app/src/pages/events/[id]/timetable/index.vue @@ -51,7 +51,12 @@ const eventIdRef = eventId const { data: eventDetail } = useEventDetail(orgId, eventId) const { data: subEvents } = useEventChildren(orgId, eventId) -const isFlatEvent = computed(() => !subEvents.value || subEvents.value.length === 0) +// Authoritative once eventDetail loads (single-row endpoint, fast). +// Pre-load it returns `false`, which keeps dayOptions empty until we +// actually know whether this is a flat event or a festival — avoids the +// `?day={festival_id}` URL flicker that occurred when subEvents was +// still pending (it was being read as "no children → flat event"). +const isFlatEvent = computed(() => eventDetail.value?.event_type === 'event') const dayOptions = computed(() => { if (isFlatEvent.value && eventDetail.value)