You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tier 1 — User-visible, multi-second cost per interaction
#
Finding
Evidence
Track
1
Initial bundle = 10.28 MB transferred on landing = 100 % of total gzipped bundle. Nothing is actually lazy-loaded despite manual chunks.
dockview audit: transfer ≈ sum of all chunk gzipped sizes (within 70 KB)
network/bundle
2
SmilesEditDialog (Ketcher) ships 6.7 MB gzip / 23 MB raw on every landing — only needed when editing molecules.
src/zndraw/static/assets/SmilesEditDialog-*.js
bundle
3
PUT /v1/rooms/{room_id}/step makes 212 sequential REST calls during 100-frame scrub = 1.4 s of round-trip wait per scrub (p50 14 ms × 100). Single biggest server-time consumer in the audit.
network-results.md §1, total 3.2 s across audit
network
4
TTI proxy = 3,489 ms in prod (canvas-visible). 2.65 s of that is post-FCP — bundle parse + hydration cost driven by 1.
plotly ships 1.5 MB gzip on every landing despite being in its own chunk. modulePreload exclusion list isn't preventing the import.
bundle summary
bundle
Tier 2 — Hundreds of ms per interaction or per-session
#
Finding
Evidence
Track
6
room-switch triggers 117 React commits/iter — 3× cold-load baseline. 70 in dockview-layout, 47 in viewer. ~140 ms cumulative React work per switch.
react-results.md §1
react
7
GET /v1/joblib/rooms/{room_id}/providers polled 133× across 24 runs (~5.5/run). 2.1 s server time. No socket-driven invalidation.
network-results.md §2
network
8
GET /v1/rooms polled 100× (~4/run). 1.7 s total server time.
network-results.md
network
9
POST /v1/auth/guest p50 47 ms, p99 82 ms — blocks socket connect → blocks canvas mount on first visit. Function-level cause unknown (py-spy gap).
network-results.md §3
network
10
Box key={remountKey} in ViewerView.tsx:51 forces Canvas remount on every room change — likely root cause of 6. WebGL context destroy/recreate on top of React unmount cascade.
code inspection + react cascade signal
react
11
GET /v1/rooms/{room_id}/geometries returns 132 KB on every call — no ETag, no diff. 7.4 MB transferred per audit (56 calls × 132 KB).
network-results.md §4
network
12
POST /v1/rooms p99 = 133 ms, max 206 ms (vs p50 10 ms) — cold-path spike on first room create per server.
network-results.md §6
network
13
scipy imported eagerly at startup — scipy/integrate/_bvp.py 199 ms + scipy/special/__init__.py 180 ms = ~400 ms of cold-start time.
python-results.md "What flames did surface"
python
14
index.modern-BBJUX7oL.js (1.2 MB raw / 336 KB gzip) — second plotly fragment, also on landing.
bundle summary
bundle
15
index main chunk = 868 KB gzip — panels/registry.tsx imports every panel synchronously (GeometryPanel, RoomsPanel, PlotsBrowserPanel, FilesystemPanel, ChatPanel, SelectionsPanel, plus SecondaryPanel + ViewerView).
dockview audit §2
bundle
Tier 3 — Tens of ms per interaction, frequent enough to add up
#
Finding
Evidence
Track
16
mui chunk = 249 KB gzip fuses x-data-grid + x-tree-view with core MUI — grid + tree are panel-scoped (Geometries/Rooms only) but co-bundled with site-wide MUI.
dockview audit §3
bundle
17
open-rooms panel-open fires 50 ms long-task in prod / 346 ms in dev — DataGrid mount + /v1/rooms fetch on first open.
dockview audit "open-rooms"
client perf
18
open-geometries panel-open fires 51 ms long-task in prod / 239 ms in dev.
dockview audit
client perf
19
GET /v1/rooms/{room_id}/edit-lock polled 56× — 1.07 s total. Why is edit-lock fetched repeatedly per session?
network-results.md
network
20
GET /v1/rooms/{room_id}/frame-selection called 56× — 1.06 s total. Selection state currently REST-pulled, not socket-pushed.
network-results.md
network
21
PUT /.../sessions/{id}/active-camera called 43× — 927 ms total. Camera state syncs per interaction.
network-results.md
network
22
GET /v1/rooms/{room_id}/bookmarks called 56× — 873 ms. Refetched on most page loads.
network-results.md
network
23
GET /v1/rooms/{room_id}/selection-groups called 56× — 783 ms. Same pattern.
network-results.md
network
24
GET /v1/rooms/{room_id}/geometries/{key:path} called 56× — 690 ms.
network-results.md
network
25
socket.io + zndraw_socketio/wrapper.py add ~500 ms to startup.
python-results.md
python
26
include_router 257 ms during startup — 25 routers registered eagerly. Architectural.
python-results.md
python
27
<module>src/zndraw/app.py self-time 277 ms — top-level imports cascade.
python-results.md
python
28
resolve_or_refresh_token 176 ms during cold-load CLI — pre-flight token check is non-trivial.
python-results.md
python
29
selection flow triggers 51 React commits per iter for 5 clicks + 1 modifier (~9 commits per click).
react-results.md §4
react
30
geometry_invalidate socket event fires 3-15× per flow — high frequency, likely triggers React cascades.
network ws-frames
network
31
room_join fires 6-21× per flow — every page navigation triggers a fresh join, with full state re-fetch downstream.
network ws-frames
network
Tier 4 — Single-digit ms or one-shot startup costs
#
Finding
Evidence
Track
32
GET /v1/rooms/{room_id}/frames called 43× — 13 ms p50, 594 ms total.
network-results.md
network
33
GET /v1/rooms/{room_id}/frames/{index}/metadata called 43× — 7 ms p50, 349 ms.
network-results.md
network
34
GET /v1/server-settings/default-room called 36× — 5 ms p50, 305 ms. Frequently re-fetched.
network-results.md
network
35
GET /v1/auth/users/me called 70× — 3 ms p50, 283 ms. Re-checked constantly.
network-results.md
network
36
active_camera_update socket event fires 3-12× per flow — modest.
network ws-frames
network
37
Dev-mode heap = 139 MB after landing (2.6× prod) — HMR + source-map overhead. Dev only, not user-visible.
Per React commit cost is consistently sub-ms (avg 0.5-0.9 ms). Good news — focus on count, not weight.
react-results.md §2
react
40
frame-scrub triggers only 39 React commits for 100 keypresses — R3F + zustand bypass works. Previously suspected "Fix F" (R3F subscription patterns from the dockview spec) is rejected on data.
Tier 1 — User-visible, multi-second cost per interaction
SmilesEditDialog(Ketcher) ships 6.7 MB gzip / 23 MB raw on every landing — only needed when editing molecules.src/zndraw/static/assets/SmilesEditDialog-*.jsPUT /v1/rooms/{room_id}/stepmakes 212 sequential REST calls during 100-frame scrub = 1.4 s of round-trip wait per scrub (p50 14 ms × 100). Single biggest server-time consumer in the audit.network-results.md§1, total 3.2 s across audit2026-04-17-dockview-perf-audit-results.mdheadlineplotlyships 1.5 MB gzip on every landing despite being in its own chunk.modulePreloadexclusion list isn't preventing the import.Tier 2 — Hundreds of ms per interaction or per-session
dockview-layout, 47 inviewer. ~140 ms cumulative React work per switch.react-results.md§1GET /v1/joblib/rooms/{room_id}/providerspolled 133× across 24 runs (~5.5/run). 2.1 s server time. No socket-driven invalidation.network-results.md§2GET /v1/roomspolled 100× (~4/run). 1.7 s total server time.network-results.mdPOST /v1/auth/guestp50 47 ms, p99 82 ms — blocks socket connect → blocks canvas mount on first visit. Function-level cause unknown (py-spy gap).network-results.md§3Box key={remountKey}inViewerView.tsx:51forces Canvas remount on every room change — likely root cause of 6. WebGL context destroy/recreate on top of React unmount cascade.GET /v1/rooms/{room_id}/geometriesreturns 132 KB on every call — no ETag, no diff. 7.4 MB transferred per audit (56 calls × 132 KB).network-results.md§4POST /v1/roomsp99 = 133 ms, max 206 ms (vs p50 10 ms) — cold-path spike on first room create per server.network-results.md§6scipy/integrate/_bvp.py199 ms +scipy/special/__init__.py180 ms = ~400 ms of cold-start time.python-results.md"What flames did surface"index.modern-BBJUX7oL.js(1.2 MB raw / 336 KB gzip) — second plotly fragment, also on landing.indexmain chunk = 868 KB gzip —panels/registry.tsximports every panel synchronously (GeometryPanel,RoomsPanel,PlotsBrowserPanel,FilesystemPanel,ChatPanel,SelectionsPanel, plusSecondaryPanel+ViewerView).Tier 3 — Tens of ms per interaction, frequent enough to add up
muichunk = 249 KB gzip fusesx-data-grid+x-tree-viewwith core MUI — grid + tree are panel-scoped (Geometries/Rooms only) but co-bundled with site-wide MUI.open-roomspanel-open fires 50 ms long-task in prod / 346 ms in dev — DataGrid mount +/v1/roomsfetch on first open.open-geometriespanel-open fires 51 ms long-task in prod / 239 ms in dev.GET /v1/rooms/{room_id}/edit-lockpolled 56× — 1.07 s total. Why is edit-lock fetched repeatedly per session?network-results.mdGET /v1/rooms/{room_id}/frame-selectioncalled 56× — 1.06 s total. Selection state currently REST-pulled, not socket-pushed.network-results.mdPUT /.../sessions/{id}/active-cameracalled 43× — 927 ms total. Camera state syncs per interaction.network-results.mdGET /v1/rooms/{room_id}/bookmarkscalled 56× — 873 ms. Refetched on most page loads.network-results.mdGET /v1/rooms/{room_id}/selection-groupscalled 56× — 783 ms. Same pattern.network-results.mdGET /v1/rooms/{room_id}/geometries/{key:path}called 56× — 690 ms.network-results.mdsocket.io+zndraw_socketio/wrapper.pyadd ~500 ms to startup.python-results.mdinclude_router257 ms during startup — 25 routers registered eagerly. Architectural.python-results.md<module>src/zndraw/app.pyself-time 277 ms — top-level imports cascade.python-results.mdresolve_or_refresh_token176 ms during cold-load CLI — pre-flight token check is non-trivial.python-results.mdreact-results.md§4geometry_invalidatesocket event fires 3-15× per flow — high frequency, likely triggers React cascades.room_joinfires 6-21× per flow — every page navigation triggers a fresh join, with full state re-fetch downstream.Tier 4 — Single-digit ms or one-shot startup costs
GET /v1/rooms/{room_id}/framescalled 43× — 13 ms p50, 594 ms total.network-results.mdGET /v1/rooms/{room_id}/frames/{index}/metadatacalled 43× — 7 ms p50, 349 ms.network-results.mdGET /v1/server-settings/default-roomcalled 36× — 5 ms p50, 305 ms. Frequently re-fetched.network-results.mdGET /v1/auth/users/mecalled 70× — 3 ms p50, 283 ms. Re-checked constantly.network-results.mdactive_camera_updatesocket event fires 3-12× per flow — modest.react-results.md§2react-results.md§3