Graph view refinements: server-persisted settings, label size, eased zoom, resize-blank fix
Five refinements to the hand-rolled renderer, all user-directed: 1. Settings are SERVER-persisted (data/render-settings.json via new GET/PUT /api/graph/render-settings) - one source of truth for every browser, replacing localStorage. The client loads once after mount, saves debounced on user changes, and never lets a slow GET clobber a faster user (or mounting PUT defaults over the server copy). The shape/defaults/sanitizer moved to src/core/render-settings.ts - pure, shared by client, server store, and tests; every disk/network value passes through sanitizeRenderSettings. 2. New "Label size" control (default 12px - the previous hardcoded size). Font, wrap width, and line height all scale; verified live: +76k px with the label gate open. 3. "Labels until zoom" slider reworked: log-scaled over 0.0625-4 so the useful band (0.25-0.5 camera-ratio units) sits at 33%/50% of the track instead of crammed at the far left of the old linear 0.25-4 slider. Display units are normalized: the default 0.5 reads as "1x", 0.25 as "0.5x". The underlying labelZoom semantics are unchanged, so existing values survive. 4. Smooth wheel zoom: d3-zoom instant per-notch jumps (x1.2, bang) are replaced by an eased glide - wheel events accumulate a cursor-anchored target scale and each tick eases the camera toward it (exponential, 120ms constant, frame-rate independent). Pan/pinch stay d3-owned; a pan cancels a glide in progress. Verified live: 8-frame glide. 5. Resize blanking fixed (graph vanished for the duration of a window drag, reappearing ~300ms after). Cause: every resize event reallocated the canvas backing store - which CLEARS it - and the redraw was deferred to rAF, which live resizes starve via relayout, so the canvas sat wiped for the whole drag. Fix: the ResizeObserver callback now redraws SYNCHRONOUSLY after reallocating (verified via a probe RO firing in the same delivery batch: canvas fully painted). kFit (the label gate 1x reference) is also recomputed on resize from the now-cached world bbox - the camera itself still persists. tests/render-settings.test.ts: 10 tests pinning the sanitizer (garbage/ partial/clamps, legacy no-labelSize records) and the slider mapping (default dead-center at 500, monotonic, exact round-trips, display units). 402/402 green; typecheck, lint, build pass.
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Server-side persistence for the graph render settings (the settings-panel
|
||||
* controls): data/render-settings.json — read-modify-write, NOT a ledger.
|
||||
*
|
||||
* The shape, defaults, and sanitizer live in src/core/render-settings.ts
|
||||
* (pure, shared with the client); this module only moves bytes. Whatever
|
||||
* comes off disk is sanitized by the caller on load — and we sanitize on
|
||||
* write too, so the file can never hold an out-of-range value that would
|
||||
* silently shift a slider.
|
||||
*/
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { RENDER_SETTINGS_FILE } from "../../config.ts";
|
||||
import { DEFAULT_RENDER_SETTINGS, sanitizeRenderSettings, type GraphRenderSettings } from "../core/render-settings.ts";
|
||||
|
||||
/** The persisted settings, or null when no file exists yet (first boot). */
|
||||
export function readRenderSettings(): GraphRenderSettings | null {
|
||||
let raw: string;
|
||||
try {
|
||||
raw = fs.readFileSync(RENDER_SETTINGS_FILE, "utf-8");
|
||||
} catch {
|
||||
return null; // never booted / unreadable → caller uses defaults
|
||||
}
|
||||
try {
|
||||
return sanitizeRenderSettings(JSON.parse(raw));
|
||||
} catch {
|
||||
return null; // corrupt → defaults, never crash the view
|
||||
}
|
||||
}
|
||||
|
||||
/** Persist settings (sanitized). Returns what was written. */
|
||||
export function writeRenderSettings(raw: unknown): GraphRenderSettings {
|
||||
const settings = sanitizeRenderSettings(raw);
|
||||
fs.mkdirSync(path.dirname(RENDER_SETTINGS_FILE), { recursive: true });
|
||||
fs.writeFileSync(RENDER_SETTINGS_FILE, JSON.stringify(settings, null, "\t"), "utf-8");
|
||||
return settings;
|
||||
}
|
||||
|
||||
export { DEFAULT_RENDER_SETTINGS };
|
||||
Reference in New Issue
Block a user