From fc6bb5485f189d25b448e98f382920141a69e676 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:59:10 +0000 Subject: [PATCH 1/2] build(deps-dev): bump eslint-plugin-react-hooks from 4.6.2 to 7.1.1 Bumps [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/HEAD/packages/eslint-plugin-react-hooks) from 4.6.2 to 7.1.1. - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/react/react/blob/main/packages/eslint-plugin-react-hooks/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/eslint-plugin-react-hooks@7.1.1/packages/eslint-plugin-react-hooks) --- updated-dependencies: - dependency-name: eslint-plugin-react-hooks dependency-version: 7.1.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package-lock.json | 59 ++++++++++++++++++++++++++++++++++++++++++----- package.json | 2 +- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f7243e..d0cad0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "electron": "^42.5.0", "electron-builder": "^26.15.3", "eslint": "^8.57.0", - "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.4.6", "jsdom": "^29.1.1", "postcss": "^8.4.38", @@ -4957,16 +4957,23 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "node_modules/eslint-plugin-react-refresh": { @@ -5702,6 +5709,23 @@ "node": ">= 0.4" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -9178,6 +9202,29 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", + "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } } } } diff --git a/package.json b/package.json index 2ec1855..104e063 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "electron": "^42.5.0", "electron-builder": "^26.15.3", "eslint": "^8.57.0", - "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.4.6", "jsdom": "^29.1.1", "postcss": "^8.4.38", From e94c55486277d3491fc0394b6e6ac39f64615076 Mon Sep 17 00:00:00 2001 From: hash00 <65664287+hash00@users.noreply.github.com> Date: Thu, 3 Sep 2026 18:05:15 +0100 Subject: [PATCH 2/2] fix: satisfy eslint-plugin-react-hooks v7 rules react-hooks/static-components: Th (ProcessesView) and SortTh (MetricsPage) were defined inside their parent's render body, so React remounted them every render. Hoisted both to module scope with explicit props instead of closures. react-hooks/incompatible-library: TanStack Virtual's useVirtualizer() has a documented API shape the rule can't verify statically; disabled inline with a reason. react-hooks/set-state-in-effect: this project isn't adopting the React Compiler, and the rule flagged two ordinary 'reset state, then load' effects as errors. Turned the rule off project-wide with a comment explaining why. --- .eslintrc.cjs | 5 ++++ src/components/LogTable.tsx | 2 ++ src/components/MetricsPage.tsx | 48 ++++++++++++++++++++------------ src/components/ProcessesView.tsx | 32 ++++++++++++++------- 4 files changed, 59 insertions(+), 28 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 6897f18..21b9916 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -15,6 +15,11 @@ module.exports = { 'warn', { allowConstantExport: true }, ], + // React Compiler-readiness rule (added in eslint-plugin-react-hooks v7); + // this project doesn't use the Compiler, and it flags the standard + // "reset state, then kick off an async load" effect pattern used + // throughout src/components as an error. + 'react-hooks/set-state-in-effect': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, diff --git a/src/components/LogTable.tsx b/src/components/LogTable.tsx index 93fd93d..472980d 100644 --- a/src/components/LogTable.tsx +++ b/src/components/LogTable.tsx @@ -289,6 +289,8 @@ export function LogTable({ entries, isStream = false, onPidClick, persist = fals const parentRef = useRef(null) const followRef = useRef(true) + // TanStack Virtual's returned functions are documented as stable; this is the library's normal API shape. + // eslint-disable-next-line react-hooks/incompatible-library const virtualizer = useVirtualizer({ count: sorted.length, getScrollElement: () => parentRef.current, diff --git a/src/components/MetricsPage.tsx b/src/components/MetricsPage.tsx index f07105b..ef608df 100644 --- a/src/components/MetricsPage.tsx +++ b/src/components/MetricsPage.tsx @@ -47,6 +47,27 @@ const Tip = ({ ) } +function SortTh({ label, k, align = 'right', cls = '', sortKey, sortDir, onToggle }: { + label: string + k: keyof ProcRow + align?: 'left' | 'right' + cls?: string + sortKey: keyof ProcRow | null + sortDir: 'asc' | 'desc' + onToggle: (k: keyof ProcRow) => void +}) { + return ( + onToggle(k)} + className={`${align === 'left' ? 'text-left' : 'text-right'} py-2 px-3 font-medium cursor-pointer select-none hover:text-body ${sortKey === k ? 'text-accent' : ''} ${cls}`} + > + {label} + {sortKey === k && (sortDir === 'asc' ? : )} + + + ) +} + function Card({ children, className = '' }: { children: React.ReactNode; className?: string }) { return
{children}
} @@ -153,16 +174,7 @@ export function MetricsPage({ entries, loading, onOpenProcess }: MetricsPageProp const axisTick = { fontSize: 10, fill: c.axis } const catTick = { fontSize: 10, fill: c.legend } - const SortTh = ({ label, k, align = 'right', cls = '' }: { label: string; k: keyof ProcRow; align?: 'left' | 'right'; cls?: string }) => ( - toggle(k)} - className={`${align === 'left' ? 'text-left' : 'text-right'} py-2 px-3 font-medium cursor-pointer select-none hover:text-body ${sortKey === k ? 'text-accent' : ''} ${cls}`} - > - {label} - {sortKey === k && (sortDir === 'asc' ? : )} - - - ) + const sortThProps = { sortKey, sortDir, onToggle: toggle } as const return (
@@ -197,14 +209,14 @@ export function MetricsPage({ entries, loading, onOpenProcess }: MetricsPageProp - - - - - - - - + + + + + + + + diff --git a/src/components/ProcessesView.tsx b/src/components/ProcessesView.tsx index 8cceeff..ea0bcfb 100644 --- a/src/components/ProcessesView.tsx +++ b/src/components/ProcessesView.tsx @@ -11,6 +11,23 @@ interface ProcessesViewProps { type Row = ProcessListItem & { logs: number } +function Th({ label, k, align = 'right', mode, sortKey, dir, onToggle }: { + label: string + k: keyof Row + align?: 'left' | 'right' + mode: 'list' | 'tree' + sortKey: keyof Row | null + dir: 'asc' | 'desc' + onToggle: (k: keyof Row) => void +}) { + return ( + + ) +} + export function ProcessesView({ entries, onOpenProcess }: ProcessesViewProps) { const [procs, setProcs] = useState([]) const [loading, setLoading] = useState(false) @@ -78,12 +95,7 @@ export function ProcessesView({ entries, onOpenProcess }: ProcessesViewProps) { return out }, [mode, procs, search, runAs, matchesFilter]) - const Th = ({ label, k, align = 'right' }: { label: string; k: keyof Row; align?: 'left' | 'right' }) => ( - - ) + const thProps = { mode, sortKey: key, dir, onToggle: toggle } as const const renderRow = (p: Row | ProcessListItem, depth = 0) => { const logs = 'logs' in p ? p.logs : (logCounts.get(p.pid) ?? 0) @@ -134,10 +146,10 @@ export function ProcessesView({ entries, onOpenProcess }: ProcessesViewProps) {
mode === 'list' && onToggle(k)} + className={`${align === 'left' ? 'text-left' : 'text-right'} py-2 px-3 font-medium whitespace-nowrap ${mode === 'list' ? 'cursor-pointer hover:text-body' : ''} ${sortKey === k && mode === 'list' ? 'text-accent' : ''}`}> + {label}{mode === 'list' && sortKey === k && (dir === 'asc' ? : )} + mode === 'list' && toggle(k)} - className={`${align === 'left' ? 'text-left' : 'text-right'} py-2 px-3 font-medium whitespace-nowrap ${mode === 'list' ? 'cursor-pointer hover:text-body' : ''} ${key === k && mode === 'list' ? 'text-accent' : ''}`}> - {label}{mode === 'list' && key === k && (dir === 'asc' ? : )} -
-
- - - + + + +