import { clsx, type ClassValue } from 'clsx' import { twMerge } from 'tailwind-merge' export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } // relativeTime renders a compact "Xs/Xm/Xh/Xd ago" label for freshness // indicators (health checks, live event timestamps, etc). export function relativeTime(iso: string | null | undefined): string { if (!iso) return 'never' const ms = Date.now() - new Date(iso).getTime() if (ms < 0) return 'just now' const s = Math.floor(ms / 1000) if (s < 60) return `${s}s ago` const m = Math.floor(s / 60) if (m < 60) return `${m}m ago` const h = Math.floor(m / 60) if (h < 24) return `${h}h ago` const d = Math.floor(h / 24) return `${d}d ago` } // Truncates long slugs/names in the middle (keeping the "type:" prefix and // the tail visible) rather than at the end — for slugs the distinguishing // part is often at both ends, e.g. "investigation:nomos/dragonfly-memlock-…" // vs "…rlimit-type-8-in-unprivileged-lxcs". export function truncateMiddle(s: string, maxLen = 36): string { if (s.length <= maxLen) return s const keep = Math.floor((maxLen - 1) / 2) return `${s.slice(0, keep)}…${s.slice(-keep)}` } // debounce wraps fn so rapid calls (e.g. keystrokes in a filter input) // collapse into one invocation after `wait`ms of silence. export function debounce void>(fn: T, wait = 300): T { let timer: ReturnType | undefined return ((...args: Parameters) => { clearTimeout(timer) timer = setTimeout(() => fn(...args), wait) }) as T } export type WithoutChild = T extends { child?: unknown } ? Omit : T export type WithoutChildren = T extends { children?: unknown } ? Omit : T export type WithoutChildrenOrChild = WithoutChildren> export type WithElementRef = T & { ref?: U | null }