fix(dashboard): refresh expiry badges when the sources rail is re-enabled

A boundary can pass while the rail is off, and an expired source has no
next boundary to wait for, so re-enabling the rail now reads the clock at
once instead of keeping the badge cached while it was disabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-09-29 20:48:56 +02:00
1 parent 97ad694549
commit a989eca4bf
2 files changed
+35 -1

No files matched your search

@@ -126,4 +126,21 @@ describe('createSourceExpiryClock', () => {
TestBed.tick();
expect(jest.getTimerCount()).toBe(1);
});
it('reads the clock at once when the sources rail is re-enabled', () => {
active.set(false);
TestBed.tick();
// Past the expiry while the rail is off: no boundary is left to wait
// for, so only the re-enable can refresh the cached badge.
facts.set(new Map([['xtream', expiringIn(60_000)]]));
TestBed.tick();
jest.advanceTimersByTime(3 * 60_000);
const before = clock();
active.set(true);
TestBed.tick();
expect(clock()).toBe(Date.now());
expect(clock()).toBeGreaterThan(before);
});
});
@@ -1,4 +1,11 @@
import { DestroyRef, effect, inject, signal, type Signal } from '@angular/core';
import {
DestroyRef,
effect,
inject,
signal,
untracked,
type Signal,
} from '@angular/core';
import { DOCUMENT } from '@angular/common';
import {
nextSourceExpiryChangeMs,
@@ -54,6 +61,16 @@ export function createSourceExpiryClock(
onCleanup(() => clearTimeout(timer));
});
// Re-enabling the rail must not show a badge cached while it was off: a
// boundary may have passed (an expired source has no next boundary to
// wait for), so read the clock at once, as becoming visible does.
let wasActive = untracked(active);
effect(() => {
const isActive = active();
if (isActive && !wasActive) untracked(() => now.set(Date.now()));
wasActive = isActive;
});
const onVisibilityChange = () => {
visible.set(!document.hidden);
if (!document.hidden) now.set(Date.now());