Clean up LSP diagnostics across backend and frontend

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).
This commit is contained in:
ARIA committed 2026-09-08 23:57:30 +02:00
1 parent 9006c22fde
commit 930e56bd07
33 files changed
+1571 -804

No files matched your search

+58 -22
View File
@@ -1,7 +1,7 @@
import { useState, useRef, useEffect } from 'react';
import { fmt } from '../../utils/units';
import type { VFPoint } from '../../types';
import { useCurveStore } from '../../store/curveStore';
import { useState, useRef, useEffect } from "react";
import { fmt } from "../../utils/units.js";
import type { VFPoint } from "../../types.js";
import { useCurveStore } from "../../store/curveStore.js";
interface Props {
point: VFPoint;
@@ -14,17 +14,28 @@ interface Props {
onMouseEnter?: () => void;
}
export function PointRow({ point, isCurrent, isSelected, isClamped, pendingDeltaKhz, shouldAutoScroll, onMouseDown, onMouseEnter }: Props) {
export function PointRow({
point,
isCurrent,
isSelected,
isClamped,
pendingDeltaKhz,
shouldAutoScroll,
onMouseDown,
onMouseEnter,
}: Props) {
const { stageEdit } = useCurveStore();
const [editing, setEditing] = useState(false);
const [inputValue, setInputValue] = useState('');
const [inputValue, setInputValue] = useState("");
const inputRef = useRef<HTMLInputElement>(null);
const trRef = useRef<HTMLTableRowElement>(null);
useEffect(() => {
if (shouldAutoScroll && trRef.current) {
// @ts-expect-error: the typing seems to not include the valid 'container' option
trRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest', container: 'nearest' });
trRef.current.scrollIntoView({
behavior: "smooth",
block: "nearest",
});
}
}, [shouldAutoScroll]);
@@ -36,11 +47,19 @@ export function PointRow({ point, isCurrent, isSelected, isClamped, pendingDelta
const displayEffMhz = point.freq_mhz + deltaChange / 1000;
const deltaColor = hasPending
? displayDeltaKhz > 0 ? 'text-cyan-400' : displayDeltaKhz < 0 ? 'text-orange-400' : 'text-zinc-400'
: point.delta_khz > 0 ? 'text-emerald-400' : point.delta_khz < 0 ? 'text-red-400' : 'text-zinc-500';
? displayDeltaKhz > 0
? "text-cyan-400"
: displayDeltaKhz < 0
? "text-orange-400"
: "text-zinc-400"
: point.delta_khz > 0
? "text-emerald-400"
: point.delta_khz < 0
? "text-red-400"
: "text-zinc-500";
function startEdit() {
setInputValue((displayDeltaMhz).toFixed(1));
setInputValue(displayDeltaMhz.toFixed(1));
setEditing(true);
setTimeout(() => {
inputRef.current?.select();
@@ -64,9 +83,13 @@ export function PointRow({ point, isCurrent, isSelected, isClamped, pendingDelta
<tr
ref={trRef}
className={[
'border-b border-zinc-800 text-xs font-mono cursor-pointer',
isCurrent ? 'bg-yellow-400/10' : isSelected ? 'bg-cyan-500/10' : 'hover:bg-zinc-800/50',
].join(' ')}
"border-b border-zinc-800 text-xs font-mono cursor-pointer",
isCurrent
? "bg-yellow-400/10"
: isSelected
? "bg-cyan-500/10"
: "hover:bg-zinc-800/50",
].join(" ")}
onMouseDown={(e) => {
if (editing) return;
onMouseDown?.(e);
@@ -80,7 +103,13 @@ export function PointRow({ point, isCurrent, isSelected, isClamped, pendingDelta
<td className="px-3 py-1 text-zinc-300">{fmt.mv(point.volt_mv, 0)}</td>
{/* Offset — click to edit inline */}
<td className={`px-3 py-1 ${deltaColor}`} onClick={(e) => { e.stopPropagation(); startEdit(); }}>
<td
className={`px-3 py-1 ${deltaColor}`}
onClick={(e) => {
e.stopPropagation();
startEdit();
}}
>
{editing ? (
<input
ref={inputRef}
@@ -90,28 +119,35 @@ export function PointRow({ point, isCurrent, isSelected, isClamped, pendingDelta
onChange={(e) => setInputValue(e.target.value)}
onBlur={commitEdit}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === 'Tab') { e.preventDefault(); commitEdit(); }
if (e.key === 'Escape') cancelEdit();
if (e.key === "Enter" || e.key === "Tab") {
e.preventDefault();
commitEdit();
}
if (e.key === "Escape") cancelEdit();
}}
className="w-20 bg-zinc-700 text-cyan-300 rounded px-1 py-0 border border-cyan-500 outline-none text-xs"
style={{ fontFamily: 'monospace' }}
className="w-20 bg-zinc-700 text-cyan-300 rounded px-1 py-0 border border-cyan-500 outline-none text-xs font-mono"
/>
) : (
<span title="Click to edit">
{hasPending && <span className="text-cyan-500 mr-0.5">✎</span>}
{displayDeltaKhz > 0 ? '+' : ''}{displayDeltaMhz.toFixed(1)} MHz
{displayDeltaKhz > 0 ? "+" : ""}
{displayDeltaMhz.toFixed(1)} MHz
</span>
)}
</td>
{/* Eff. Freq */}
<td className={`px-3 py-1 font-semibold ${hasPending ? 'text-cyan-200' : 'text-zinc-100'}`}>
<td
className={`px-3 py-1 font-semibold ${hasPending ? "text-cyan-200" : "text-zinc-100"}`}
>
{fmt.mhz(displayEffMhz, 0)}
{isClamped && !hasPending && (
<span
className="ml-1 text-amber-500 cursor-help"
title="Clamped by monotonicity — a lower-voltage point with a higher offset is holding this frequency up"
>⇡</span>
>
⇡
</span>
)}
</td>
@@ -1,8 +1,11 @@
import { useState, useMemo, useEffect } from 'react';
import { PointRow } from './PointRow';
import type { VFPoint } from '../../types';
import { findCurrentPoint, detectClampedPoints } from '../../utils/curveHelpers';
import { useCurveStore } from '../../store/curveStore';
import { useState, useMemo, useEffect } from "react";
import { PointRow } from "./PointRow.js";
import type { VFPoint } from "../../types.js";
import {
findCurrentPoint,
detectClampedPoints,
} from "../../utils/curveHelpers.js";
import { useCurveStore } from "../../store/curveStore.js";
interface Props {
points: VFPoint[];
@@ -11,22 +14,32 @@ interface Props {
}
export function PointTable({ points, currentVoltageMv, readOnly }: Props) {
const { pendingDeltas, selectedPoints, selectPoint, selectRange } = useCurveStore();
const {
pendingDeltas,
selectedPoints,
selectPoint,
togglePoint,
selectRange,
} = useCurveStore();
const currentPoint = findCurrentPoint(points, currentVoltageMv);
const clampedPoints = useMemo(() => detectClampedPoints(points), [points]);
const [dragStartIdx, setDragStartIdx] = useState<number | null>(null);
useEffect(() => {
function onUp() { setDragStartIdx(null); }
window.addEventListener('mouseup', onUp);
return () => window.removeEventListener('mouseup', onUp);
function onUp() {
setDragStartIdx(null);
}
window.addEventListener("mouseup", onUp);
return () => window.removeEventListener("mouseup", onUp);
}, []);
return (
<div className="bg-zinc-900 rounded-lg overflow-hidden flex flex-col">
<div className="flex items-center gap-2 px-3 py-2 border-b border-zinc-800 shrink-0">
<span className="text-xs text-zinc-500 uppercase tracking-wider mr-2">Points</span>
<span className="text-xs text-zinc-500 uppercase tracking-wider mr-2">
Points
</span>
{!readOnly && pendingDeltas.size > 0 && (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-cyan-500/15 border border-cyan-500/30 text-cyan-400 text-xs">
{pendingDeltas.size} staged
@@ -35,13 +48,17 @@ export function PointTable({ points, currentVoltageMv, readOnly }: Props) {
{readOnly && (
<span className="text-xs text-zinc-600 italic">read-only</span>
)}
<span className="ml-auto text-xs text-zinc-600">{points.length} points</span>
<span className="ml-auto text-xs text-zinc-600">
{points.length} points
</span>
{!readOnly && selectedPoints.size === 1 && (
<div className="flex gap-1 ml-4 border-l border-zinc-800 pl-4">
<button
onClick={() => {
const idx = Array.from(selectedPoints)[0];
const beforeIdxs = points.filter(p => p.index <= idx).map(p => p.index);
const beforeIdxs = points
.filter((p) => p.index <= idx)
.map((p) => p.index);
selectRange(beforeIdxs);
}}
className="px-2 py-0.5 rounded text-xs bg-zinc-800 text-zinc-400 hover:bg-zinc-700 transition-colors whitespace-nowrap"
@@ -51,7 +68,9 @@ export function PointTable({ points, currentVoltageMv, readOnly }: Props) {
<button
onClick={() => {
const idx = Array.from(selectedPoints)[0];
const afterIdxs = points.filter(p => p.index >= idx).map(p => p.index);
const afterIdxs = points
.filter((p) => p.index >= idx)
.map((p) => p.index);
selectRange(afterIdxs);
}}
className="px-2 py-0.5 rounded text-xs bg-zinc-800 text-zinc-400 hover:bg-zinc-700 transition-colors whitespace-nowrap"
@@ -66,9 +85,15 @@ export function PointTable({ points, currentVoltageMv, readOnly }: Props) {
<thead className="sticky top-0 bg-zinc-900 z-10">
<tr className="text-xs text-zinc-500 uppercase tracking-wider border-b border-zinc-800">
<th className="px-3 py-2 text-left font-normal bg-zinc-900">#</th>
<th className="px-3 py-2 text-left font-normal bg-zinc-900">Voltage</th>
<th className="px-3 py-2 text-left font-normal bg-zinc-900">Offset</th>
<th className="px-3 py-2 text-left font-normal bg-zinc-900">Eff. Freq</th>
<th className="px-3 py-2 text-left font-normal bg-zinc-900">
Voltage
</th>
<th className="px-3 py-2 text-left font-normal bg-zinc-900">
Offset
</th>
<th className="px-3 py-2 text-left font-normal bg-zinc-900">
Eff. Freq
</th>
<th className="px-3 py-2 text-left font-normal bg-zinc-900" />
</tr>
</thead>
@@ -81,34 +106,48 @@ export function PointTable({ points, currentVoltageMv, readOnly }: Props) {
isSelected={selectedPoints.has(p.index)}
isClamped={clampedPoints.has(p.index)}
pendingDeltaKhz={pendingDeltas.get(p.index)}
shouldAutoScroll={selectedPoints.size === 1 && selectedPoints.has(p.index)}
onMouseDown={readOnly ? undefined : (e) => {
if (e.shiftKey) {
const currentSelected = Array.from(selectedPoints);
if (currentSelected.length > 0) {
const last = Math.max(...currentSelected);
const min = Math.min(last, p.index);
const max = Math.max(last, p.index);
const toSelect = points.filter(a => a.index >= min && a.index <= max).map(a => a.index);
selectRange(toSelect);
} else {
selectPoint(p.index, false);
}
} else if (e.ctrlKey || e.metaKey) {
selectPoint(p.index, true);
} else {
setDragStartIdx(p.index);
selectPoint(p.index, false);
}
}}
onMouseEnter={readOnly ? undefined : () => {
if (dragStartIdx !== null) {
const min = Math.min(dragStartIdx, p.index);
const max = Math.max(dragStartIdx, p.index);
const toSelect = points.filter(a => a.index >= min && a.index <= max).map(a => a.index);
selectRange(toSelect);
}
}}
shouldAutoScroll={
selectedPoints.size === 1 && selectedPoints.has(p.index)
}
onMouseDown={
readOnly
? undefined
: (e) => {
if (e.shiftKey) {
const currentSelected = Array.from(selectedPoints);
if (currentSelected.length > 0) {
const last = Math.max(...currentSelected);
const min = Math.min(last, p.index);
const max = Math.max(last, p.index);
const toSelect = points
.filter((a) => a.index >= min && a.index <= max)
.map((a) => a.index);
selectRange(toSelect);
} else {
selectPoint(p.index);
}
} else if (e.ctrlKey || e.metaKey) {
togglePoint(p.index);
} else {
setDragStartIdx(p.index);
selectPoint(p.index);
}
}
}
onMouseEnter={
readOnly
? undefined
: () => {
if (dragStartIdx !== null) {
const min = Math.min(dragStartIdx, p.index);
const max = Math.max(dragStartIdx, p.index);
const toSelect = points
.filter((a) => a.index >= min && a.index <= max)
.map((a) => a.index);
selectRange(toSelect);
}
}
}
/>
))}
</tbody>