GenerAI / worldmonitor /src /components /news /source-coverage.ts
amogaddy's picture
Integra World Monitor (AGPL-3.0, self-hosted) nello Space: pagina, menu, e arricchimento notizie per la AI (part 7)
9d2d895 verified
Raw
History Blame Contribute Delete
2.9 kB
/**
* Whether β€” and how β€” a news panel discloses that it is showing only part of
* its source list (#5873).
*
* A CUSTOM news category (a panel the user added from another variant) is never
* in the per-variant server digest, so direct per-feed fetch is its only path
* and that path is capped. The cap now rotates and the panel accumulates across
* refresh cycles, but for the first cycles the panel genuinely holds only some
* of its sources while the Sources manager lists all of them as enabled. Saying
* so is the difference between a panel that is partial and one that lies about
* being complete.
*
* The decision lives here rather than in NewsPanel so its edge cases β€” full
* coverage, an empty source list, an unresolved dictionary β€” are unit-testable
* without standing up a panel and a DOM.
*/
export interface SourceCoverage {
/** Distinct sources actually represented in what the panel is rendering. */
covered: number;
/** Sources the user has enabled for the category. */
total: number;
}
export type CoverageStringKey = 'sourceCoverage' | 'sourceCoverageHint';
export type CoverageTranslate = (key: string, vars: Record<string, string>) => string;
/**
* The badge detail (`"3/10 sources"`) or its explanatory title, or `undefined`
* when the panel has nothing to disclose and should read plain `LIVE`.
*
* Returns `undefined` for four distinct reasons, all of which must stay silent
* rather than render something wrong:
*
* β€’ `null` coverage β€” the digest-backed / preset case, i.e. almost every panel.
* β€’ Full coverage β€” rotation gets there within a few cycles, and a permanent
* "10/10 sources" would be noise on every custom panel forever.
* β€’ A non-positive total, or a `covered` that somehow exceeds it. `covered` is
* derived from rendered items and `total` from the enabled feed list, so a
* source disabled between the two reads could produce "11/10 sources".
* β€’ An unresolved key. These strings sit outside the first-paint i18n shell
* deliberately (the badge only appears after a completed news load), so the
* lazy dictionary may not have landed. An unresolved key composes into
* `LIVE Β· components.newsPanel.sourceCoverage`, which the raw-key healer
* cannot repair β€” it only rewrites EXACT key matches.
*/
export function coverageBadgeString(
coverage: SourceCoverage | null,
key: CoverageStringKey,
translate: CoverageTranslate,
): string | undefined {
if (!coverage) return undefined;
const { covered, total } = coverage;
if (!Number.isFinite(covered) || !Number.isFinite(total)) return undefined;
if (total <= 0 || covered < 0 || covered >= total) return undefined;
const path = `components.newsPanel.${key}`;
const translated = translate(path, { covered: String(covered), total: String(total) });
return translated === path ? undefined : translated;
}