fix(frontend): auto-refresh relative dates as time passes

Relative dates ("5 minutes ago", "in 2 hours") were computed once via
dayjs().fromNow() and never recomputed, so a view left open kept showing
the value from the moment it was rendered.

Compute the relative string against the shared, ticking `now` from
useGlobalNow() instead. This makes every reactive caller — <TimeDisplay>,
direct formatDateSince() calls, and formatDisplayDate() when the user's
date display is set to relative — re-render on the existing 60s tick.
Absolute date formats don't read `now`, so they never needlessly
re-render.

useGlobalNow can now be initialised from a plain helper rather than only
from a component, so its route-update hook is guarded with
getCurrentInstance().
This commit is contained in:
kolaente 2026-06-17 21:42:21 +02:00
parent 55ca06ca3d
commit 40156d0b60
2 changed files with 16 additions and 6 deletions

View File

@ -1,4 +1,4 @@
import { ref } from 'vue'
import { getCurrentInstance, ref } from 'vue'
import { createGlobalState, useIntervalFn } from '@vueuse/core'
import { onBeforeRouteUpdate } from 'vue-router'
@ -18,10 +18,14 @@ export const useGlobalNow = createGlobalState(() => {
useIntervalFn(update, GLOBAL_NOW_INTERVAL, { immediate: true })
// ensure the now value is refreshed when the route changes
onBeforeRouteUpdate(() => {
update()
})
// Now that this state can be initialised from a plain helper (formatDateSince), the
// first caller is not guaranteed to be a component — guard the route hook accordingly.
if (getCurrentInstance()) {
// ensure the now value is refreshed when the route changes
onBeforeRouteUpdate(() => {
update()
})
}
return {
now,

View File

@ -5,6 +5,7 @@ import {i18n} from '@/i18n'
import {createSharedComposable} from '@vueuse/core'
import {computed, toValue, type MaybeRefOrGetter} from 'vue'
import {useDateDisplay} from '@/composables/useDateDisplay'
import {useGlobalNow} from '@/composables/useGlobalNow'
import {useTimeFormat} from '@/composables/useTimeFormat'
import {DATE_DISPLAY, type DateDisplay} from '@/constants/dateDisplay'
import {TIME_FORMAT, type TimeFormat} from '@/constants/timeFormat'
@ -49,8 +50,13 @@ export const formatDateSince = (date: Date | string | null) => {
const locale = DAYJS_LOCALE_MAPPING[i18n.global.locale.value.toLowerCase()] ?? 'en'
// Computing the relative string against the shared, ticking `now` (instead of fromNow's
// internal Date.now()) makes every reactive caller re-render on the 60s tick, so open views
// don't keep showing a stale "x minutes ago".
const {now} = useGlobalNow()
return date
? dayjs(date).locale(locale).fromNow()
? dayjs(date).locale(locale).from(now.value)
: ''
}