| 1 | import type { Item } from "./useController"; |
| 2 | import { t } from "./i18n"; |
| 3 | |
| 4 | export interface WireReadPause { |
| 5 | code?: string; |
| 6 | id: string; |
| 7 | reads: { readId: string; path: string; snapshot?: string; intent?: string; covered?: [number, number][]; missing?: [number, number][]; reason: string }[]; |
| 8 | omitted?: number; |
| 9 | } |
| 10 | |
| 11 | function rangesLabel(ranges: [number, number][] | undefined): string { |
| 12 | return (Array.isArray(ranges) ? ranges : []).slice(0, 64) |
| 13 | .filter(r => Array.isArray(r) && Number.isInteger(r[0]) && Number.isInteger(r[1]) && r[0] >= 0 && r[1] > r[0]) |
| 14 | .map(([start, end]) => `${start + 1}–${end}`).join(", "); |
| 15 | } |
| 16 | |
| 17 | export function readPauseItem(pause: WireReadPause | undefined, fallbackID: string): Item { |
| 18 | const reasons = new Set<string>(); |
| 19 | const details = (Array.isArray(pause?.reads) ? pause.reads : []).slice(0, 32).map(read => { |
| 20 | const reasonKey = read.reason === "no_progress" ? "composer.readStatusStalled" |
| 21 | : ["page_budget", "time_budget", "no_headroom", "unknown_window"].includes(read.reason) ? "composer.readStatusBudget" : "composer.readStatusSource"; |
| 22 | const covered = rangesLabel(read.covered); |
| 23 | const missing = rangesLabel(read.missing); |
| 24 | reasons.add(t(reasonKey)); |
| 25 | return [read.path, t(reasonKey), covered && t("notice.readPauseCovered", { range: covered }), missing && t("notice.readPauseMissing", { range: missing })].filter(Boolean).join(" · "); |
| 26 | }); |
| 27 | if (pause?.omitted) details.push(t("notice.readPauseOmitted", { count: pause.omitted })); |
| 28 | return { kind: "notice", id: pause?.id ? `read-pause-${pause.id}` : fallbackID, level: "info", code: "incomplete_read", title: t("notice.readPauseTitle"), text: [...reasons, t("notice.readPauseBody")].join(" "), detail: details.join("\n") }; |
| 29 | } |
| 30 | |
| 31 | export function upsertReadPause(items: Item[], pause: WireReadPause | undefined, fallbackID: string): Item[] { |
| 32 | const item = readPauseItem(pause, fallbackID); |
| 33 | const index = items.findIndex(existing => existing.id === item.id); |
| 34 | if (index < 0) return [...items, item]; |
| 35 | return items.map((existing, i) => i === index ? item : existing); |
| 36 | } |
| 37 |