LoVisual/frontend/src
loki5512344 6fa36f8b32
perf(frontend): memoize ColorField/GradientField with stable callbacks
React.memo alone does nothing here: PaletteTabs built a fresh
`(hex) => setColor(slot, hex)` closure per field on every render, so
memo's prop comparison always saw a "changed" onChange and re-rendered
anyway. Stabilized it properly:

- setColor/setGradient read draft/onChange from a ref (updated in a
  useLayoutEffect, not during render -- oxlint's react(refs) rule
  correctly flags a ref write in the render body) so they keep a
  stable identity (useCallback, empty deps) across edits
- per-slot wrapper closures are built once in a useMemo keyed off
  those now-stable functions, not off `draft`

This doesn't change the preset-switch case measured earlier (every
field's value genuinely changes then, so memo correctly re-renders
all of them -- that cost is what startTransition already covers). It
does stop sibling fields re-rendering on interactions that don't
touch them: undo/redo, tab switching, hovering unrelated panel
elements.

Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
2026-09-30 10:58:34 +02:00
..
app feat(frontend): avatar upload QOL — feedback, re-crop, drop, progress, history 2026-09-28 22:56:01 +02:00
features perf(frontend): memoize ColorField/GradientField with stable callbacks 2026-09-30 10:58:34 +02:00
pages feat(frontend): full redesign of login/register 2026-09-30 10:30:53 +02:00
shared feat(download): two-button page with the jar served by our backend 2026-09-29 18:39:40 +02:00
test feat(frontend): avatar upload QOL — feedback, re-crop, drop, progress, history 2026-09-28 22:56:01 +02:00
index.css perf(frontend): stop theme switches from janking the whole page 2026-09-30 10:37:40 +02:00
main.tsx chore(history): squash 16 commit(s) from 2026-09-26 2026-09-26 15:26:37 +02:00