@hyperpackai/eslint-plugin
v0.8.0
Published
ESLint rules for Hyperion — catches the reactivity mistakes that fail silently.
Readme
@hyperpackai/eslint-plugin
Lint rules for Hyperion. Both rules here catch mistakes that fail silently — the page renders, nothing throws, and a value simply never updates. Those are the expensive ones, because you find them by noticing the UI is wrong.
npm install --save-dev @hyperpackai/eslint-plugin eslint// eslint.config.js
import hyperion from "@hyperpackai/eslint-plugin";
export default [hyperion.configs.recommended];hyperion/no-untracked-signal-read
A component body runs once. signal.value read there is a snapshot — fine as
an initial value, silently frozen for anything that changes.
// ✗ renders once, never updates
<div>{count.value}</div>
// ✗ spinner forever
if (loading.value) return <Spinner />;
// ✓ a getter re-runs
<div>{() => count.value}</div>
// ✓ the signal itself is reactive when passed through
<div>{count}</div>
// ✓ Show subscribes to the condition
<Show when={loading}>{() => <Spinner />}</Show>The rule reports only the two shapes that are always wrong: a read inside a
JSX expression container, and a read in a condition that produces JSX. Reads in
nested functions — getters, event handlers, effects — re-run later and are never
reported. A read kept as a deliberate snapshot (const initial = count.value)
is not reported either.
hyperion/no-peek-in-lazy-children
Children of Show, Match, Keyed and Transition are built when the
parent renders, not when the branch opens.
// ✗ read at parent mount, before anything set the message
<Show when={open}><p>{message.peek()}</p></Show>
// ✓ a function child defers the read to when the branch opens
<Show when={open}>{() => <p>{message.peek()}</p>}</Show>when={flag.peek()} is not reported — attributes are evaluated eagerly on
purpose.
What these rules found
Run over this repository's own documentation site, no-untracked-signal-read
caught three live bugs in the icon gallery: an aria-pressed state that never
changed after the first render, and two form bindings that ignored programmatic
updates. None of them threw; all three had shipped.
