Add ESLint + pre-commit hook and fix the issues it surfaced

This commit is contained in:
2026-06-05 01:42:42 +03:00
parent 3d38dd1411
commit 939950b136
10 changed files with 1543 additions and 53 deletions
+45 -41
View File
@@ -36,50 +36,54 @@ export function LivePreview() {
if (!node) return;
const text = draftText.trim();
const timer = setTimeout(async () => {
// Empty/blank is not an error — clean, empty pane (spec §04).
if (text === '') {
handleRef.current?.destroy();
handleRef.current = null;
setError(null);
return;
}
let parsed: unknown;
try {
parsed = JSON.parse(text);
} catch (e) {
setError(`Invalid JSON: ${(e as Error).message}`);
return;
}
const mine = ++generationRef.current;
try {
const prepared = prepareSpecForRender(parsed, { fitMode: 'default' });
const config = chartConfigFor(uiTheme);
handleRef.current?.destroy();
handleRef.current = null;
const handle = await renderSpec(node, prepared as VisualizationSpec, config);
if (mine !== generationRef.current) {
// TODO: a superseded render's destroy() calls node.replaceChildren(),
// which can blank the live chart if two embeds on the same node are
// ever in flight at once (heavy spec whose embed outlasts the 300ms
// debounce). The debounce makes this rare in M1; when fit-mode/dataset
// work (M2/M3) lands, serialize renders or finalize the stale view
// without clearing the shared node.
handle.destroy(); // a newer render superseded this one
// The debounced body is async; wrap in a void IIFE so the timer callback
// returns void (it handles its own errors internally — nothing awaits it).
const timer = setTimeout(() => {
void (async () => {
// Empty/blank is not an error — clean, empty pane (spec §04).
if (text === '') {
handleRef.current?.destroy();
handleRef.current = null;
setError(null);
return;
}
handleRef.current = handle;
setError(null);
} catch (e) {
if (mine === generationRef.current) {
setError(
`Rendering error: ${(e as Error).message}. ` +
`Check your JSON syntax and that the spec is valid Vega-Lite.`,
);
let parsed: unknown;
try {
parsed = JSON.parse(text);
} catch (e) {
setError(`Invalid JSON: ${(e as Error).message}`);
return;
}
}
const mine = ++generationRef.current;
try {
const prepared = prepareSpecForRender(parsed, { fitMode: 'default' });
const config = chartConfigFor(uiTheme);
handleRef.current?.destroy();
handleRef.current = null;
const handle = await renderSpec(node, prepared as VisualizationSpec, config);
if (mine !== generationRef.current) {
// TODO: a superseded render's destroy() calls node.replaceChildren(),
// which can blank the live chart if two embeds on the same node are
// ever in flight at once (heavy spec whose embed outlasts the 300ms
// debounce). The debounce makes this rare in M1; when fit-mode/dataset
// work (M2/M3) lands, serialize renders or finalize the stale view
// without clearing the shared node.
handle.destroy(); // a newer render superseded this one
return;
}
handleRef.current = handle;
setError(null);
} catch (e) {
if (mine === generationRef.current) {
setError(
`Rendering error: ${(e as Error).message}. ` +
`Check your JSON syntax and that the spec is valid Vega-Lite.`,
);
}
}
})();
}, RENDER_DEBOUNCE_MS);
return () => clearTimeout(timer);