Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ jobs:
python -m pip install --upgrade uv
uv venv
uv pip install -e ".[test]"
uv run pytest --ignore=tests/test_browser
uv run pytest --ignore=tests/test_e2e

browser-tests:
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -51,5 +51,5 @@ jobs:
- name: Install Playwright system deps
if: steps.pw-cache.outputs.cache-hit == 'true'
run: uv run playwright install-deps chromium
- name: Pytest (browser tests)
run: uv run pytest tests/test_browser -v
- name: Pytest (e2e tests)
run: uv run pytest tests/test_e2e -v
8 changes: 7 additions & 1 deletion agents.md → AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ syncs back to Python.
| TextCompare | `wigglystuff.text_compare.TextCompare` | `text_a`, `text_b`, `matches`, `selected_match`, `min_match_words` | Side-by-side text diff with match highlighting |
| EnvConfig | `wigglystuff.env_config.EnvConfig` | `variables`, `all_valid` | Environment variable config with validation |
| ModuleTreeWidget | `wigglystuff.module_tree.ModuleTreeWidget` | `tree`, `initial_expand_depth` | Interactive tree viewer for PyTorch nn.Module |
| WandbChart | `wigglystuff.wandb_chart.WandbChart` | `api_key`, `entity`, `project`, `key`, `poll_seconds`, `smoothing_kind`, `smoothing_param`, `show_slider`, `width`, `height` | Live line chart that polls wandb for metric data with smoothing |
| Neo4jWidget | `wigglystuff.neo4j_widget.Neo4jWidget` | `nodes`, `relationships`, `schema`, `error`, `query_running`, `selected_nodes`, `selected_relationships`, `width`, `height` | Interactive Neo4j graph explorer with Cypher query input |
| SplineDraw | `wigglystuff.spline_draw.SplineDraw` | `data`, `curve`, `curve_error`, `brushsize`, `n_classes`, `width`, `height` | Draw scatter points with Python-computed spline curve fitting |
| ScatterWidget | re-exported from [`drawdata`](https://github.com/koaning/drawdata) | `data`, `brushsize`, `width`, `height`, `n_classes` | Paint multi-class 2D scatter data with brush |
Expand Down Expand Up @@ -101,3 +100,10 @@ syncs back to Python.
- When planning a new widget, always present the proposed Python API
(constructor, traitlets, helper methods) during plan review so the user
can sign off on the interface before implementation.
- E2E test fixtures (`tests/fixtures/*.py`, `demos/*.py` used by Playwright
tests) must live inside the project tree. The repo's `pyproject.toml`
sets `[tool.marimo.runtime] auto_instantiate = true` so cells run on
notebook open — without that, `marimo edit` shows the editor but never
produces widget output and selectors will time out. If a test needs an
isolated copy, put the copy somewhere a `pyproject.toml` walk reaches,
not under `tmp_path`.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@

All notable changes to this project will be documented in this file.

## [Unreleased]
## 0.4.0

### Fixed

- `EnvConfig` no longer syncs configured secret values in its exported anywidget state, preventing marimo static HTML exports from embedding environment-loaded or manually entered secrets.

### Removed

- `WandbChart` widget removed due to a security concern: marimo's static HTML export embeds all anywidget traitlets, which would leak the user-supplied `api_key` into any exported notebook.

## [0.3.5] - 2026-04-23

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ These widgets depend on 3rd party packages. They still ship with wigglystuff but
<table>
<tr>
<td align="center"><b>ModuleTreeWidget</b><br><a href="https://molab.marimo.io/notebooks/nb_K7QvvoASZErgKxwD8XSMWi"><img src="./mkdocs/assets/gallery/moduletree.png" width="330"></a><br><a href="https://molab.marimo.io/notebooks/nb_K7QvvoASZErgKxwD8XSMWi">molab</a> · <a href="https://koaning.github.io/wigglystuff/reference/module-tree/">API</a> · <a href="https://koaning.github.io/wigglystuff/reference/module-tree.md">MD</a></td>
<td align="center"><b>WandbChart</b><br><a href="https://molab.marimo.io/notebooks/nb_pbN8i6DyggB26Xrzw9Bztw"><img src="./mkdocs/assets/gallery/wandbchart.png" width="330"></a><br><a href="https://molab.marimo.io/notebooks/nb_pbN8i6DyggB26Xrzw9Bztw">molab</a> · <a href="https://koaning.github.io/wigglystuff/reference/wandb-chart/">API</a> · <a href="https://koaning.github.io/wigglystuff/reference/wandb-chart.md">MD</a></td>
<td align="center"><b>Neo4jWidget</b><br><a href="https://molab.marimo.io/notebooks/nb_ghifaw8nRCuDAgc1UTajXU"><img src="./mkdocs/assets/gallery/neo4j-widget.png" width="330"></a><br><a href="https://molab.marimo.io/notebooks/nb_ghifaw8nRCuDAgc1UTajXU">molab</a> · <a href="https://koaning.github.io/wigglystuff/reference/neo4j-widget/">API</a> · <a href="https://koaning.github.io/wigglystuff/reference/neo4j-widget.md">MD</a></td>
<td align="center"><b>AltairWidget</b><br><a href="https://molab.marimo.io/github/koaning/wigglystuff/blob/main/demos/altairwidget.py"><img src="./mkdocs/assets/gallery/altairwidget.png" width="330"></a><br><a href="https://molab.marimo.io/github/koaning/wigglystuff/blob/main/demos/altairwidget.py">molab</a> · <a href="https://koaning.github.io/wigglystuff/reference/altair-widget/">API</a> · <a href="https://koaning.github.io/wigglystuff/reference/altair-widget.md">MD</a></td>
</tr>
Expand Down
9 changes: 8 additions & 1 deletion demos/envconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import marimo

__generated_with = "0.19.4"
__generated_with = "0.23.3"
app = marimo.App(width="medium")


Expand All @@ -17,6 +17,7 @@ def _():
import os
import marimo as mo
from wigglystuff import EnvConfig

return EnvConfig, mo, os


Expand Down Expand Up @@ -68,6 +69,12 @@ def _(mo):
return


@app.cell
def _(config_validated):
"MY_API_KEY" in config_validated
return


@app.cell
def _(config_validated):
config_validated["MY_API_KEY"]
Expand Down
4 changes: 3 additions & 1 deletion demos/sortlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
# "wigglystuff==0.3.1",
# ]
# ///

import marimo

__generated_with = "0.18.1"
__generated_with = "0.23.3"
app = marimo.App(width="columns", sql_output="polars")


@app.cell
def _():
import marimo as mo
from wigglystuff import SortableList

return SortableList, mo


Expand Down
1 change: 0 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ nav:
- ProgressBar: reference/progress-bar.md
- PulsarChart: reference/pulsar-chart.md
- ModuleTreeWidget: reference/module-tree.md
- WandbChart: reference/wandb-chart.md
- Neo4jWidget: reference/neo4j-widget.md
- ScatterWidget: reference/scatter-widget.md
- DiffViewer: reference/diff-viewer.md
Expand Down
Binary file removed mkdocs/assets/gallery/wandbchart.png
Binary file not shown.
5 changes: 0 additions & 5 deletions mkdocs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,6 @@ These widgets depend on 3rd party packages. They still ship with wigglystuff but
<div class="gallery-links"><a target="_blank" href="https://molab.marimo.io/notebooks/nb_K7QvvoASZErgKxwD8XSMWi">molab</a><a href="reference/module-tree/">API</a><a href="reference/module-tree.md">MD</a></div>
</div>
<div class="gallery-item">
<div class="gallery-title">WandbChart</div>
<a target="_blank" href="https://molab.marimo.io/notebooks/nb_pbN8i6DyggB26Xrzw9Bztw" class="gallery-img"><img src="assets/gallery/wandbchart.png" alt="WandbChart widget"></a>
<div class="gallery-links"><a target="_blank" href="https://molab.marimo.io/notebooks/nb_pbN8i6DyggB26Xrzw9Bztw">molab</a><a href="reference/wandb-chart/">API</a><a href="reference/wandb-chart.md">MD</a></div>
</div>
<div class="gallery-item">
<div class="gallery-title">Neo4jWidget</div>
<a target="_blank" href="https://molab.marimo.io/notebooks/nb_ghifaw8nRCuDAgc1UTajXU" class="gallery-img"><img src="assets/gallery/neo4j-widget.png" alt="Neo4jWidget"></a>
<div class="gallery-links"><a target="_blank" href="https://molab.marimo.io/notebooks/nb_ghifaw8nRCuDAgc1UTajXU">molab</a><a href="reference/neo4j-widget/">API</a><a href="reference/neo4j-widget.md">MD</a></div>
Expand Down
1 change: 0 additions & 1 deletion mkdocs/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,4 @@ These widgets depend on 3rd party packages. They still ship with wigglystuff but

- [AltairWidget](https://koaning.github.io/wigglystuff/reference/altair-widget.md): Flicker-free Altair chart rendering with smooth data updates via the Vega View API
- [ModuleTreeWidget](https://koaning.github.io/wigglystuff/reference/module-tree.md): Interactive tree viewer for PyTorch nn.Module architecture
- [WandbChart](https://koaning.github.io/wigglystuff/reference/wandb-chart.md): Live line chart that polls wandb for metric data with configurable smoothing
- [Neo4jWidget](https://koaning.github.io/wigglystuff/reference/neo4j-widget.md): Interactive Neo4j graph explorer with Cypher query input, autocomplete, and lasso selection
2 changes: 1 addition & 1 deletion mkdocs/reference/env-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

| Traitlet | Type | Notes |
| --- | --- | --- |
| `variables` | `list` | List of variable info dicts with name, status, error, has_validator, value. |
| `variables` | `list` | List of variable info dicts with name, status, error, and has_validator. Secret values are not synced. |
| `all_valid` | `bool` | True when all variables are valid. |

## Helper methods
Expand Down
18 changes: 0 additions & 18 deletions mkdocs/reference/wandb-chart.md

This file was deleted.

10 changes: 8 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "wigglystuff"
version = "0.3.5"
version = "0.4.0"
description = "Collection of Anywidget Widgets"
readme = "README.md"
requires-python = ">=3.10"
Expand Down Expand Up @@ -32,6 +32,7 @@ neo4j = [
]
test = [
"pytest>=8.3.3",
"marimo>=0.23.3",
"scikit-learn>=1.0",
"matplotlib>=3.0",
"pandas>=2.3.3",
Expand All @@ -40,7 +41,7 @@ test = [
test-browser = [
"pytest>=8.3.3",
"pytest-playwright>=0.6.2",
"marimo>=0.18.0",
"marimo>=0.23.3",
]
docs = [
"altair>=6.0.0",
Expand Down Expand Up @@ -68,3 +69,8 @@ artifacts = ["wigglystuff/static/*"]

[tool.marimo.runtime]
auto_instantiate = true

[tool.pytest.ini_options]
markers = [
"e2e: browser/server tests that exercise full user flows",
]
10 changes: 9 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import time
import socket
import pytest
from pathlib import Path


def find_free_port():
Expand All @@ -25,7 +26,12 @@ def test_something(start_marimo, page):
"""
servers = []

def _start(notebook_path: str) -> str:
def _start(
notebook_path: str,
*,
env: dict[str, str] | None = None,
cwd: str | Path | None = None,
) -> str:
port = find_free_port()
proc = subprocess.Popen(
[
Expand All @@ -36,6 +42,8 @@ def _start(notebook_path: str) -> str:
"--port", str(port),
notebook_path,
],
cwd=cwd,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
Expand Down
29 changes: 29 additions & 0 deletions tests/fixtures/envconfig_export_notebook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import marimo

__generated_with = "0.23.3"
app = marimo.App(width="medium")


@app.cell
def _():
import marimo as mo
from wigglystuff import EnvConfig

config = mo.ui.anywidget(EnvConfig(["WIGGLYSTUFF_EXPORT_SECRET"]))
config
return (config,)


@app.cell
def _(config):
'WIGGLYSTUFF_EXPORT_SECRET' in config
return


@app.cell
def _():
return


if __name__ == "__main__":
app.run()
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
import marimo

__generated_with = "0.19.4"
__generated_with = "0.23.3"
app = marimo.App(width="medium")


@app.cell
def _():
import marimo as mo
from wigglystuff import TangleSlider, TangleChoice, TangleSelect

return TangleChoice, TangleSelect, TangleSlider, mo


@app.cell
def _(TangleSlider, mo):
coffees = mo.ui.anywidget(TangleSlider(amount=10, min_value=0, max_value=100, step=1, suffix=" coffees", digits=0))
coffees
return (coffees,)
return


@app.cell
def _(TangleChoice, mo):
emoji = mo.ui.anywidget(TangleChoice(choices=["smile", "party", "boom"]))
emoji
return (emoji,)
return


@app.cell
def _(TangleSelect, mo):
veggie = mo.ui.anywidget(TangleSelect(choices=["potato", "carrot", "apple"]))
veggie
return (veggie,)
return


if __name__ == "__main__":
Expand Down
70 changes: 70 additions & 0 deletions tests/test_e2e/test_env_config_browser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Playwright integration test: EnvConfig must not leak manual entries into HTML downloads."""

from __future__ import annotations

import os
from pathlib import Path

import pytest
from playwright.sync_api import Page, TimeoutError as PlaywrightTimeoutError, expect


SECRET_NAME = "WIGGLYSTUFF_EXPORT_SECRET"
SECRET_VALUE = "wigglystuff-secret-red-test-123"
NOTEBOOK = Path(__file__).parent.parent / "fixtures" / "envconfig_export_notebook.py"
ROOT = Path(__file__).parents[2]


@pytest.mark.e2e
def test_env_config_ui_download_omits_manually_entered_secret(
tmp_path: Path,
page: Page,
start_marimo,
):
"""EnvConfig should not leak manually entered values into HTML downloads."""
notebook_source = NOTEBOOK.read_text(encoding="utf-8")
assert SECRET_NAME in notebook_source
assert SECRET_VALUE not in notebook_source

# Run from the in-tree fixture path so marimo picks up the project's
# `[tool.marimo.runtime] auto_instantiate = true`. Copying the notebook
# to tmp_path leaves it without project context and the cells never run.
env = os.environ.copy()
env.pop(SECRET_NAME, None)

url = start_marimo(str(NOTEBOOK), env=env, cwd=ROOT)
page.goto(url, wait_until="networkidle")
page.wait_for_selector(".env-config-widget", timeout=10_000)

env_input = page.locator(".env-input")
env_input.fill(SECRET_VALUE)
env_input.press("Enter")
expect(page.locator(".env-config-row")).to_have_attribute(
"data-status",
"valid",
timeout=5_000,
)
expect(page.locator("pre", has_text="True")).to_be_visible(timeout=5_000)

command = page.get_by_text(
"Download > Download as HTML (exclude code)",
exact=True,
)
for shortcut in ("Meta+K", "Control+K"):
page.keyboard.press(shortcut)
try:
command.wait_for(state="visible", timeout=1_000)
break
except PlaywrightTimeoutError:
page.keyboard.press("Escape")
else:
command.wait_for(state="visible", timeout=1_000)

with page.expect_download(timeout=15_000) as download_info:
command.click()

html = Path(download_info.value.path()).read_text(encoding="utf-8")
assert "True" in html
assert SECRET_VALUE not in html, (
"EnvConfig leaked the manually entered secret into the downloaded HTML."
)
Loading
Loading