whisper-money/resources/js/lib
Víctor Falcón 5b3400909e
fix(chart): stop tooltip render loop crashing the dashboard (PHP-LARAVEL-3B) (#657)
## Problem

Sentry **PHP-LARAVEL-3B** — React `Maximum update depth exceeded`
crashing `/dashboard`. Regressed, 5 occurrences / 4 users (mobile
Chrome/Android and Safari), last seen today.

## Root cause

`ChartTooltipPortal` in `resources/js/components/ui/chart.tsx`
positioned the portaled tooltip in a `useLayoutEffect` with **no
dependency array**, so it ran after every render and called `setPos`. An
equality guard was the only thing preventing a `render → effect → setPos
→ render` feedback loop, and it failed once the computed position
oscillated by a sub-pixel (fractional `getBoundingClientRect` / integer
`offsetWidth`). React then aborted after 50 nested updates, taking down
the dashboard.

## Fix

1. **`651c7d72`** — Depend on the primitive `coordinate.x/.y` (and
`offset`) so the effect runs only when the cursor moves, never as a
result of its own `setPos`. `coordinate` itself is excluded on purpose:
Recharts hands a fresh object every render, so depending on the object
would reopen the loop. Extract the flip/clamp math into a pure
`computeTooltipPosition` helper (`resources/js/lib/`) rounded to whole
pixels, and unit-test it.
2. **`1c1602db`** — Component regression test: asserts the positioning
effect does **not** re-run on a re-render with an unchanged coordinate,
and **does** on a coordinate change. Verified it fails against the
pre-fix dependency-less effect.
3. **`0354a031`** — Strengthen the helper test to assert sub-pixel
positions collapse to one pixel, and correct the doc note (the
dependency array removes the loop; rounding only narrows the
oscillation).

## Review

Two independent review agents (architecture/duplication/tests +
user-facing correctness) examined the diff:
- **Correctness**: no must-fix issues. First paint is correct (measured
at `-9999`/opacity 0 before paint, capped by a viewport-relative
`max-w`, so size is accurate and there is no flash). The loop cannot
recur through the `setHidden` effect or a repeated identical coordinate.
No mobile/edge regression — the flip/clamp logic is the extracted
original.
- **Architecture**: helper placement/naming/types match conventions; no
duplicate positioning logic elsewhere to fold in; both agents' top
request (regression-test the actual fix mechanism) is addressed by
commit 2. `ChartTooltipPortal` is exported for the test, matching how
`StackedBarShape` is exported for its own test.

## Known minor residual (acceptable)

If the tooltip's **content size** changes while the cursor stays on the
exact same point *and* it sits at a viewport edge, the flip/clamp won't
recompute until the next mouse move. In practice content size changes
together with the coordinate (Recharts hover). Worst case is a one-frame
cosmetic overflow that self-corrects — strictly better than the crash it
replaces.

## Testing

`chart-tooltip-position.test.ts` (5) + `chart-tooltip-portal.test.tsx`
(2) pass. Note: the infinite loop itself can't be reproduced in jsdom
(zero-size rects), so the component test guards the mechanism (effect
does not self-retrigger) rather than the throw.

Fixes PHP-LARAVEL-3B
2026-07-07 18:39:09 +00:00
..
balance-import-config-storage.ts fix: add SSR guards to localStorage/sessionStorage access 2025-12-08 18:19:20 +01:00
banking-connections.test.ts fix(open-banking): only block re-adding a bank when a live connection exists (#569) 2026-06-20 13:09:04 +02:00
banking-connections.ts fix(open-banking): only block re-adding a bank when a live connection exists (#569) 2026-06-20 13:09:04 +02:00
category-tree.test.ts feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
category-tree.ts feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
chart-calculations.test.ts fix(dashboard): treat loans as debt in net worth (#238) 2026-03-20 09:55:53 +00:00
chart-calculations.ts feat(accounts): add real estate asset tracking (#241) 2026-03-24 10:21:32 +00:00
chart-tooltip-position.test.ts fix(chart): stop tooltip render loop crashing the dashboard (PHP-LARAVEL-3B) (#657) 2026-07-07 18:39:09 +00:00
chart-tooltip-position.ts fix(chart): stop tooltip render loop crashing the dashboard (PHP-LARAVEL-3B) (#657) 2026-07-07 18:39:09 +00:00
chunk-load-recovery.test.ts Fix Vite asset preload recovery (#399) 2026-05-14 15:59:08 +02:00
chunk-load-recovery.ts Fix Vite asset preload recovery (#399) 2026-05-14 15:59:08 +02:00
connect-providers.tsx feat(banking): enable Interactive Brokers for all users (#593) 2026-06-26 11:03:21 +02:00
crypto.ts refactor(encryption): strip client-side transaction encryption (#514) 2026-06-20 16:13:26 +00:00
csrf.test.ts refactor(js): extract shared getCsrfToken util (#475) 2026-06-03 17:26:09 +02:00
csrf.ts refactor(js): extract shared getCsrfToken util (#475) 2026-06-03 17:26:09 +02:00
cursor-pagination.test.ts Fix Sentry transaction and dashboard crashes (#372) 2026-05-10 11:10:31 +01:00
cursor-pagination.ts Fix Sentry transaction and dashboard crashes (#372) 2026-05-10 11:10:31 +01:00
debug.ts fix: make useIsMobile hook and utility functions SSR-safe 2025-12-08 17:33:54 +01:00
dexie-db.ts fix(sync): don't crash when IndexedDB is unavailable (PHP-LARAVEL-43) (#654) 2026-07-07 04:59:10 +00:00
file-parser.test.ts fix(import): honor selected date format for CSV imports (#494) 2026-06-05 15:10:48 +02:00
file-parser.ts fix(import): honor selected date format for CSV imports (#494) 2026-06-05 15:10:48 +02:00
import-config-storage.ts Fix transaction importer account preselect (#396) 2026-05-14 12:38:44 +02:00
key-storage.ts refactor(encryption): strip client-side transaction encryption (#514) 2026-06-20 16:13:26 +00:00
media-query.test.ts fix(appearance): support MediaQueryList change events on legacy Safari (PHP-LARAVEL-41) (#646) 2026-07-05 17:03:35 +00:00
media-query.ts fix(appearance): support MediaQueryList change events on legacy Safari (PHP-LARAVEL-41) (#646) 2026-07-05 17:03:35 +00:00
new-transactions.test.ts feat(transactions): make new-transaction marker cross-device (#611) 2026-06-29 19:11:37 +02:00
new-transactions.ts feat(transactions): make new-transaction marker cross-device (#611) 2026-06-29 19:11:37 +02:00
orphan-components.test.ts chore(frontend): add orphan component detection and remove dead components (#181) 2026-03-02 12:43:27 +01:00
posthog.test.ts Harden browser storage and PostHog recording (#402) 2026-05-14 15:57:48 +02:00
posthog.ts feat(posthog): route analytics through reverse proxy (#463) 2026-06-01 09:29:30 +02:00
rule-builder-utils.test.ts feat(transactions): add counterparty fields (#440) 2026-05-27 16:20:55 +02:00
rule-builder-utils.ts feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
rule-engine-parity.test.ts refactor: consolidate duplicated financial calculations (#643) 2026-07-04 22:26:44 +02:00
rule-engine.ts feat(transactions): add counterparty fields (#440) 2026-05-27 16:20:55 +02:00
sankey-utils.ts feat: Group small expending categories on the Sankey chart 2026-01-11 19:04:20 +01:00
sentry.test.ts fix(sentry): drop browser-extension noise before sending events (#568) 2026-06-20 12:47:05 +02:00
sentry.ts fix(sentry): drop browser-extension noise before sending events (#568) 2026-06-20 12:47:05 +02:00
subscription-payment-issue-toast.test.ts feat: keep past due subscriptions active (#416) 2026-05-22 10:19:11 +01:00
subscription-payment-issue-toast.ts feat: keep past due subscriptions active (#416) 2026-05-22 10:19:11 +01:00
sync-manager.test.ts fix(sync): don't crash when IndexedDB is unavailable (PHP-LARAVEL-43) (#654) 2026-07-07 04:59:10 +00:00
sync-manager.ts fix(sync): don't crash when IndexedDB is unavailable (PHP-LARAVEL-43) (#654) 2026-07-07 04:59:10 +00:00
transaction-delete-confirmation.test.ts Fix Spanish translations in category and delete flows (#397) 2026-05-14 11:36:53 +01:00
transaction-delete-confirmation.ts Fix Spanish translations in category and delete flows (#397) 2026-05-14 11:36:53 +01:00
transaction-filter-serialization.ts feat(transactions): save and reuse transaction filters (#496) 2026-06-05 18:00:14 +02:00
transaction-re-evaluation.test.ts Add labels to automation rules (#379) 2026-05-11 14:56:25 +02:00
transaction-re-evaluation.ts Add labels to automation rules (#379) 2026-05-11 14:56:25 +02:00
utils.ts Prevent duplicate notes when re-evaluating automation rules 2025-12-04 16:42:54 +01:00