Skip to content

Freeze size of heavy nodes when resizing panels#812

Draft
krassowski wants to merge 7 commits into
jupyterlab:mainfrom
krassowski:split-panel-throttle
Draft

Freeze size of heavy nodes when resizing panels#812
krassowski wants to merge 7 commits into
jupyterlab:mainfrom
krassowski:split-panel-throttle

Conversation

@krassowski
Copy link
Copy Markdown
Member

Description

Trigger condition

  • using the Performance API works but we can detect the longtask or long-animation-frame but it comes after user already saw a freeze for a few seconds; I think this is not the right approach - the Performance API was designed primarily as a measurement tool, not to make the rendering system reactive.

Freeze implementation

  • using contain: strict alone is not enough because it tells the layout engine that when considering layout of parents it can use the size as given in width/height but the browser still needs to relay all the text nodes inside the widget, which is more expensive
  • we can address this by using minWidth/maxWidth on the widgets with many children; it works but it is still not as fast as it could be; we know that it could be faster because resizing the same panels top-down is super smooth (without tricks) since we enabled strict containment. I believe that the difference is that the leaf layout, including text reflow, is never touched in top-down resize because the content is just scrolled rather than re-laid
  • freezing the sizes of all leaves of heavy widgets does give a smooth resizing experience; this allows the rendering pipeline to skip layout of the contents altogether when resizing
  • I was also experimenting with applying content-visibility: auto to DOM children of heavy widgets, I am yet to confirm if this is warranted in the final form

Freezing the width means that the Widget in panels which have overflow: auto enabled get scrollbars, which you can see on the recordings below.

Throttled/delayed updates

  • we can update periodically to ensure user gets some idea of how the content will look like; the more often we perform the update, the more jittery the resize is. In recordings below I am using updates once every 3 seconds
  • we can update when user pauses resizing; currently I wait 300ms to decide if user paused resizing
  • the updates are staggered, doing things one panel (group) at a time, spread across frames to avoid any long tasks/dropped frames

Before

Screencast.From.2026-04-21.15-21-43.webm

After (active on pointer down, no long animation frame detection)

Screencast.From.2026-04-21.15-11-26.webm

After (activates only after long frame/task detection)

Screencast.From.2026-04-21.14-52-24.webm

@krassowski
Copy link
Copy Markdown
Member Author

Testing locally using scripted JupyterLab galata benchmarks, we can drop most optimizations apart for dimension freezing:

Setup

Two pre-rendered notebooks opened side-by-side (horizontal split):

Notebook Content
print_output_notebook 1 cell · 1 stream output · 10 000 lines of qwertyuiopasdfghjklzxcvbnm
display_output_notebook 1 cell · 10 000 separate display_data outputs

Measurement: browser performance.measure() over a full drag gesture - pointer-down on the divider handle, move 200 px right, move back, pointer-up (40 synthetic pointer events via Playwright).
Browser: Chromium · 3 samples per configuration.

Results

Configuration What is applied Mean (ms) Δ vs no optimisation
No optimisation - 1453 -
contain: strict only contain: strict on the panel head 1421 −2 % (noise)
content-visibility: auto only content-visibility: auto + contain-intrinsic-* on children 1507 +4 % (overhead)
Freeze dimensions only width/min-width/max-width, height/min-height fixed to measured rect 727 −50 %
All (current code) All of the above combined 757 −48 %

krassowski added a commit to jupyterlab/jupyterlab that referenced this pull request May 19, 2026
## References

- Same idea as jupyterlab/lumino#812
- Addresses jupyterlab/lumino#505 but for now
in JupyterLab rather than lumino

This will allow us to get feedback on this change during the beta cycle,
without having to commit to extended API surface in lumino yet.

## Code changes

- Adds `optimizeResize` Shell setting
- Adds `OptimizedDockPanelSvg` subclass of `DockPanelSvg`
- Uses `OptimizedDockPanelSvg` in the `LabShell`

## User-facing changes

Resizing is faster. This can be verified by running:

```bash
BENCHMARK_NUMBER_SAMPLES=20 jlpm test:benchmark --grep "Notebook Resize"
```

| Mode        | n  | avg     | median  | sd    | min     | max     |
|-------------|-----|---------|---------|-------|---------|---------|
| optimized   | 20 | 736 ms  | 731 ms  | 19 ms | 718 ms  | 804 ms  |
| unoptimized | 20 | 1444 ms | 1440 ms | 16 ms | 1422 ms | 1473 ms |

<details>

<summary>To recreate table above</summary>

```python
import json, statistics
data = json.load(open('~/jupyterlab/galata/benchmark-results/lab-benchmark.json'))
by_mode = {}
for r in data['values']:
    by_mode.setdefault(r['file'], []).append(r['time'])

for name, times in sorted(by_mode.items()):
    n = len(times)
    avg = sum(times)/n
    med = statistics.median(times)
    sd = statistics.stdev(times)
    print(f'{name}: n={n}  avg={avg:.0f}ms  median={med:.0f}ms  sd={sd:.0f}ms  min={min(times):.0f}ms  max={max(times):.0f}ms')
```

</details>


### Before

[Screencast From 2026-04-21
15-21-43.webm](https://github.com/user-attachments/assets/2776d939-209a-4313-a214-73d057d24fbb)

### After

[Screencast From 2026-04-21
15-11-26.webm](https://github.com/user-attachments/assets/b7550824-f4fe-4ff2-8570-c34bdb0caafc)


## Backwards-incompatible changes

None

## AI usage

- **Yes**: Some or all of the content of this PR was generated by AI.
- **Yes**: The human author has carefully reviewed this PR and run this
code (keep this PR "draft" until the answer is YES)
- AI tools and models used: Claude Code, Codex

---------

Co-authored-by: Darshan Poudel <pranishpoudel10@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant