fix(ui): companion popup waits for sustained calm; connection banner stops crying wolf
Demo images / Build & push demo images (push) Successful in 2m36s
Demo images / Build & push demo images (push) Successful in 2m36s
Two "unpolished" moments, worst on the demo: - CompanionIntroOverlay gated on a point-in-time check of the reveal cinematic flag. On a cold cache the entrance video can start buffering after the 5s base delay, so the flag was still false when sampled and the popup cut into the cinematic anyway. It now shows only after the scene has been continuously calm for the full grace window. - ConnectionBanner's flat 2.5s debounce fired on every tab-return and on first dashboard paint: a dead WebSocket is the NORMAL state right then (browsers kill background-tab sockets; first paint races the initial connect), and reconnects routinely exceed 2.5s over real links. Those moments now get a 10s runway (15s window after load/resume); genuine mid-session drops keep the 2.5s response. On the demo the blip banner is suppressed entirely — it runs against a local mock, so "Connection lost" there is pure noise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
547f674ac8
commit
1a3170f1c3
@@ -75,7 +75,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import * as QRCode from 'qrcode'
|
||||
import { IS_DEMO } from '@/composables/useDemoIntro'
|
||||
import { useLoginTransitionStore } from '@/stores/loginTransition'
|
||||
@@ -101,6 +101,8 @@ const loginTransition = useLoginTransitionStore()
|
||||
const BASE_DELAY_MS = 5000
|
||||
const POST_INTRO_GRACE_MS = 2000
|
||||
|
||||
let calmTicker: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
onMounted(() => {
|
||||
try {
|
||||
if (localStorage.getItem(STORAGE_KEY) !== '1') {
|
||||
@@ -111,21 +113,29 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (calmTicker) clearInterval(calmTicker)
|
||||
})
|
||||
|
||||
function maybeShow() {
|
||||
if (loginTransition.introCinematicPlaying) {
|
||||
// First-visit reveal is still running — hold until it finishes.
|
||||
const stop = watch(
|
||||
() => loginTransition.introCinematicPlaying,
|
||||
(playing) => {
|
||||
if (!playing) {
|
||||
stop()
|
||||
setTimeout(() => { visible.value = true }, POST_INTRO_GRACE_MS)
|
||||
}
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
visible.value = true
|
||||
// Show only after the scene has been CONTINUOUSLY calm (no reveal
|
||||
// cinematic) for the full grace window. The previous point-in-time check
|
||||
// raced a slow-starting reveal — on a cold cache the entrance video can
|
||||
// begin buffering after the 5s base delay, so the flag was still false
|
||||
// when sampled and the popup cut straight into the cinematic.
|
||||
let calmSince = loginTransition.introCinematicPlaying ? null : Date.now()
|
||||
calmTicker = setInterval(() => {
|
||||
if (loginTransition.introCinematicPlaying) {
|
||||
calmSince = null
|
||||
return
|
||||
}
|
||||
if (calmSince === null) calmSince = Date.now()
|
||||
if (Date.now() - calmSince >= POST_INTRO_GRACE_MS) {
|
||||
if (calmTicker) clearInterval(calmTicker)
|
||||
calmTicker = null
|
||||
visible.value = true
|
||||
}
|
||||
}, 250)
|
||||
}
|
||||
|
||||
watch(visible, async (isVisible) => {
|
||||
|
||||
Reference in New Issue
Block a user