Skip to content

Commit 7ec2dd5

Browse files
koaningclaude
andauthored
Fix Slider2D infinite recursion on latest marimo (#82)
* Fix Slider2D infinite recursion on latest marimo The widget was triggering an infinite loop because syncFromModel() called drawSlider() which updated the model, triggering change events that called syncFromModel() again. Add updateModel parameter to drawSlider() to prevent model updates when syncing from model state. Bump version from 0.2.9 to 0.2.10 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add CHANGELOG.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 411e617 commit 7ec2dd5

4 files changed

Lines changed: 20 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
## [0.2.10] - 2026-01-07
6+
7+
### Fixed
8+
- Fixed "Maximum call stack size exceeded" error when rendering `Slider2D` widget on latest marimo. The widget had an infinite loop where model change handlers would trigger redraws that updated the model again.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "wigglystuff"
3-
version = "0.2.9"
3+
version = "0.2.10"
44
description = "Collection of Anywidget Widgets"
55
readme = "README.md"
66
requires-python = ">=3.10"

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wigglystuff/static/2dslider.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ function render({ model, el }) {
44
canvas.setAttribute("width", model.get("width").toString());
55
canvas.setAttribute("height", model.get("height").toString());
66
canvas.setAttribute("style", "border: 1px solid #ccc; background: #eee;");
7-
7+
88
const sliderValuesDiv = document.createElement("div");
99
sliderValuesDiv.setAttribute("id", "sliderValues");
10-
10+
1111
el.appendChild(canvas);
1212
el.appendChild(sliderValuesDiv);
1313

@@ -21,7 +21,7 @@ function render({ model, el }) {
2121
let currentX = 0;
2222
let currentY = 0;
2323

24-
function drawSlider() {
24+
function drawSlider(updateModel = true) {
2525
ctx.clearRect(0, 0, canvas.width, canvas.height);
2626

2727
ctx.beginPath();
@@ -39,9 +39,11 @@ function render({ model, el }) {
3939
const mappedY = y_min + ((-currentY / radius) + 1) / 2 * (y_max - y_min); // Y is inverted
4040

4141
sliderValuesDiv.textContent = `X: ${mappedX.toFixed(2)}, Y: ${mappedY.toFixed(2)}`;
42-
model.set('x', mappedX);
43-
model.set('y', mappedY);
44-
model.save_changes();
42+
if (updateModel) {
43+
model.set('x', mappedX);
44+
model.set('y', mappedY);
45+
model.save_changes();
46+
}
4547
}
4648

4749
function syncFromModel() {
@@ -53,9 +55,9 @@ function render({ model, el }) {
5355
// Inverse mapping from user coordinates to pixel coordinates
5456
currentX = radius * (2 * (modelX - x_min) / (x_max - x_min) - 1);
5557
currentY = -radius * (2 * (modelY - y_min) / (y_max - y_min) - 1); // Y is inverted
56-
58+
5759
if (!isDragging) {
58-
drawSlider();
60+
drawSlider(false); // Don't update model when syncing from model
5961
}
6062
}
6163

0 commit comments

Comments
 (0)