Backend (nvcurve/): - hal/fans.py, hal/limits.py, hal/gpu.py: replace conditional pynvml imports with the established 'pynvml: Any = _pynvml_import' pattern (fixes ~50 'possibly unbound' errors); type the result dicts; guard query_interface() results; explicit uuid/pci-bus parsing (int, hex convention documented); modernize Optional[T] -> T | None - cli.py: fix 'curve_state' possibly-unbound and snap_path None handling in cmd_setup; wrap unchecked int()/open()/makedirs() calls in try/except with clean CLI errors; add module logger for silent except-pass blocks; raise ... from exc; fix unused loop vars and set-comprehension - hal/snapshot.py: filepath: str | None; wrap all file ops; sorted imports; remove unused CT_POINTS import - daemon.py: extract 0o666 to _SOCKET_MODE constant (intentional for /run sockets) with nosemgrep - server.py: nosemgrep for Python 3.7-compat false positive (project requires >= 3.12); log previously-swallowed exception - profiles/native.py, profiles/apply.py: wrap file ops and int(k) profile-key parsing; sorted imports; modernize typing Frontend (frontend/src): - Add .js extensions to all relative imports (standard TS-ESM; Vite resolves .js -> .ts) - React.FormEvent (deprecated in React 19 types) -> React.SubmitEvent - catch (e: any) -> catch (e: unknown) + instanceof Error narrowing - React-hooks: move ref writes from render into effects; convert viewport reset to render-phase state adjustment; split selectPoint(index, multi) into selectPoint + togglePoint (no flag argument); remove non-null assertion - Static inline styles -> Tailwind classes (dynamic positioning/cursor styles kept) - Remove non-standard 'container' option from scrollIntoView (browsers ignore unknown options) which had orphaned a @ts-expect-error - Object.fromEntries for Map -> Record conversion Tooling: - .gitignore: ignore .codegraph/ local tool data Verified: tsc --noEmit, vite production build, python imports, and full LSP scan (0 errors/warnings in both projects).
63 lines
2.5 KiB
TypeScript
63 lines
2.5 KiB
TypeScript
import { fmt } from '../../utils/units.js';
|
|
import type { VFPoint } from '../../types.js';
|
|
|
|
interface Props {
|
|
point: VFPoint;
|
|
/** Pending delta in kHz (from store), if any */
|
|
pendingDeltaKhz?: number;
|
|
/** True if this point is being held up by monotonicity enforcement */
|
|
isClamped?: boolean;
|
|
}
|
|
|
|
/**
|
|
* HTML tooltip rendered as an absolutely-positioned overlay on top of the
|
|
* SVG so it's never clipped by the SVG viewport.
|
|
*/
|
|
export function CurveTooltip({ point, pendingDeltaKhz, isClamped }: Props) {
|
|
const hasPending = pendingDeltaKhz !== undefined;
|
|
const pendingMhz = hasPending ? pendingDeltaKhz! / 1000 : 0;
|
|
const deltaChange = hasPending ? pendingDeltaKhz! - point.delta_khz : 0;
|
|
const pendingEffMhz = hasPending ? point.freq_mhz + deltaChange / 1000 : null;
|
|
|
|
return (
|
|
<div
|
|
className="absolute right-4 bottom-4 pointer-events-none z-50 w-[172px] bg-zinc-800 border border-zinc-700 rounded-md p-2 text-xs shadow-xl"
|
|
>
|
|
<div className="text-zinc-400 mb-1">Point {point.index}</div>
|
|
<div className="text-zinc-200">
|
|
<span className="text-zinc-400">Volt: </span>{fmt.mv(point.volt_mv, 1)}
|
|
</div>
|
|
<div className="text-zinc-200">
|
|
<span className="text-zinc-400">Offset: </span>
|
|
<span className={point.delta_khz > 0 ? 'text-emerald-400' : point.delta_khz < 0 ? 'text-red-400' : 'text-zinc-400'}>
|
|
{point.delta_khz > 0 ? '+' : ''}{fmt.mhz(point.delta_mhz, 1)}
|
|
</span>
|
|
</div>
|
|
<div className="text-emerald-300 font-semibold">
|
|
<span className="text-zinc-400">Eff.: </span>{fmt.mhz(point.freq_mhz, 0)}
|
|
{isClamped && <span className="text-amber-500 ml-1">⇡</span>}
|
|
</div>
|
|
{isClamped && (
|
|
<div className="text-amber-500/80 text-[10px] mt-0.5">
|
|
Clamped by lower-voltage point
|
|
</div>
|
|
)}
|
|
{hasPending && (
|
|
<>
|
|
<div className="border-t border-zinc-700 mt-1.5 pt-1.5">
|
|
<div className="text-zinc-200">
|
|
<span className="text-zinc-400">Pending: </span>
|
|
<span className={pendingMhz > 0 ? 'text-cyan-400' : pendingMhz < 0 ? 'text-orange-400' : 'text-zinc-400'}>
|
|
{pendingMhz > 0 ? '+' : ''}{pendingMhz.toFixed(1)} MHz
|
|
</span>
|
|
</div>
|
|
<div className="text-cyan-300 font-semibold">
|
|
<span className="text-zinc-400">→ Eff.: </span>{fmt.mhz(pendingEffMhz, 0)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|