Commit Graph
2 Commits
Author SHA1 Message Date
Thomas Camlong 933a54ead9 docs(perf): consolidated before/after, plus what is still unverified
All previous numbers were spread across rounds with different instruments and
different workloads. This is one instrument, one workload, alternated, 2 runs per
side, merge-base vs HEAD:

  server idle        141.9 -> 89.7 MiB   (-37%)
  server loaded      257.3 -> 173.0 MiB  (-33%)
  server peak        705.2 -> 568.4 MiB  (-19%)
  after idle soak    332.0 -> 232.5 MiB  (-30%)
  JS event listeners 10,126 -> 7,774     (-23%)
  React fibers       5,224 -> 3,588      (-31%)
  DOM nodes          9,175 -> 8,284      (-10%)
  browser JS heap    55.2 -> 51.1 MiB    (-7%)
  image              410 -> 398 MB       (-3%)
  container CPU      22.8 -> 22.8 s      unchanged

Recorded prominently rather than buried: under *sustained* 4-tab load the two builds
converge, 368 vs 364 MiB. The wins are idle, steady state and peak. That is why
hammering the app with five tabs did not feel 30% lighter — at saturation the
footprint is live widget data and render churn, which this PR does not change.

Also documents the gaps, since "tested" without them would be misleading: no
multi-hour soak (the #5301 reports are about days), single user, SQLite only, the
DevTools Profiler tab confirmed at bundle level rather than in the extension, and
the App Router re-rendering ~1x/second still unfixed.

verify-context-menu.mts now also covers touch long-press, and no longer asserts on a
fiber total — that counter swung 7,454 to 16,149 between runs of the same build, so
it was noise. probe-fiber-tree.mts is the stable counter and is what the -31% above
comes from.
2026-08-09 15:39:41 +02:00
Thomas Camlong b4be884dfc perf(board): one shared context menu instead of one per widget
Mantine's Menu mounts Popover + PopoverDropdown + several context providers even
while closed, so a menu per widget was ~13 fibers each for something almost never
opened. WidgetContextMenuProvider now mounts a single menu for the board, anchored
to the cursor; items keep only a contextmenu/long-press listener.

The dropdown takes the section's gridstack ref as a prop rather than reading
SectionContext — the shared menu lives outside any section, and reading it from
context there crashed the board with "useSectionContext must be used within a
SectionContext".

Verified against both builds with scripts/benchmarks/verify-context-menu.mts
(opens on right-click, shows actions, closes on Escape, re-targets to a second
widget):

  idle fibers          18,670 -> 16,278  (-12.8%)
  fibers after use     +4,191 -> +4
  JS event listeners   10,128 -> 7,774   (-23%)
  DOM nodes             9,189 -> 8,287   (-10%)
  browser JS heap       54.1  -> 52.7 MiB
  median board load      520  -> 479 ms
  container CPU         20.4  -> 20.0 s

Two runs per side, four tabs across two boards. Server memory is unchanged, as
expected for a client-only change. The listener reduction is the point: the 650 MB
session had ~88k listeners and this removes a per-widget source of them.

The gate is validated by passing against unmodified code first. An earlier version
used Playwright's click({ button: "right" }), which emits no contextmenu event
here and so failed shipped code identically — it wrongly condemned a previous
attempt at this optimisation. A gate never run against the un-optimised build
proves only that something changed, not that it still works.

pnpm test 1794/1795 (pre-existing pi-hole Testcontainers flake, 9/9 in isolation);
typecheck clean across 37 packages; 0 lint errors.
2026-08-09 11:05:03 +02:00