Skip to main content

lumut

Lumut—moss in Indonesian—is an MIT-licensed collection of marimo-focused AnyWidgets and an experimental ground for creating new notebook widgets. It is heavily inspired by wigglystuff: small Python APIs, self-contained frontend bundles, and practical notebook demos. The first widgets explore data editing; future experiments do not need to be tables.

The exp_data_editor showing wrapped text rows
exp_data_editor
molab · API · Markdown
The data_editor_enhance Glide editor showing wrapped text rows
data_editor_enhance (Glide rough resize experiment)
molab · API · Markdown

The preview is an illustration of the widget. The MoLab demo runs the actual AnyWidget from this repository.

Current experiments

The current collection contains two data-editor experiments. They are useful testbeds for marimo integration, interaction design, and AnyWidget packaging; they are not intended to replace marimo's production ui.data_editor for large datasets.

Both editors accept the documented marimo.ui.data_editor inputs: eager Pandas-, Polars-, or PyArrow-style dataframes, a list of scalars (shown in a value column), records, and column-oriented mappings. They support label, on_change, editable_columns, column_sizing_mode, pagination, and page_size; their edited value is normalized to a list of row dictionaries for the AnyWidget transport.

Rows are measured only after they are rendered. Wrapped content can grow a row, but never beyond max_row_height. Resizing a column triggers a remeasurement of rendered rows, rather than an O(N) pass over all data.

Why this does not use Glide Data Grid

Glide Data Grid remains a capable canvas-based editor, but its public rowHeight API is either one constant or a callback indexed by row. In the current implementation, the callback path requires the grid to walk row heights to derive total height and map a scroll offset to a row. A cached callback therefore does not make exact variable wrapped-row height suitable for very large datasets. The upstream "fit content" request was discussed as a consumer-side canvas-measurement workaround rather than delivered as a native auto-height API.

Lumut instead uses TanStack Virtual's dynamic measurement model: unseen rows have an estimate, rendered rows are measured, and the virtualizer adjusts geometry as measurements arrive. max_row_height makes that refinement bounded and predictable after column resizing.

Maintenance is a second consideration. Glide's most recent stable GitHub release is 6.0.3 (February 2024), while development since then has appeared as intermittent 6.0.4 alpha commits. That does not mean the project is abandoned, but it makes an upstream change to its scroll geometry a higher-risk dependency for an experimental component. Lumut depends on the MIT-licensed TanStack Table and TanStack Virtual projects instead, and owns only the narrow editor surface it needs.

References:

Install and use

Install the published package with:

uv pip install lumut

For local development, build the JavaScript bundle before using the editable package:

uv pip install -e .
npm install
npm run build
import marimo as mo
from lumut import exp_data_editor

editor = mo.ui.anywidget(exp_data_editor(
    [
        {"name": "Ada", "notes": "A long value that can wrap naturally."},
        {"name": "Grace", "notes": "Another editable row."},
    ],
    label="People",
    editable_columns=["notes"],
    max_row_height=160,
))

editor

editor.value is the edited list of row dictionaries.

column_sizing_mode, pagination, and page_size remain available for compatibility with marimo's documented API, even though marimo marks them deprecated.

Glide rough resize experiment

data_editor_enhance is the Glide-based resize experiment. It is a Glide Data Grid implementation of the same small input/value contract as exp_data_editor and mo.ui.data_editor, with wrapped_columns added. During a wrapped-column resize it estimates a single capped row height from the visible row window plus a 20-row buffer. On pointer release it applies that sampled height to all rows. This avoids an O(N) measurement pass while dragging but is an approximation, not content-fit auto-height: off-screen rows can be over- or under-sized.

It deliberately pins the browser bundle to React 18: Glide 6.0.3 declares React 16–18 peer support, whereas the TanStack-only editor has no such Glide constraint.

Use this only for experimentation with eager small-to-medium data. Glide's variable rowHeight callback still makes its scroll geometry a poor fit for a million-row exact-auto-height editor. For a measured, bounded-height design, use exp_data_editor and continue the planned windowed-data work in issue #1.

from lumut import data_editor_enhance

glide_editor = data_editor_enhance(
    [{"notes": "Resize the notes column to try rough wrapping."}],
    editable_columns=["notes"],
    wrapped_columns=["notes"],
    max_row_height=160,
)

Scope

This first experiment includes typed cell editing, column resize, wrapped text, capped dynamic heights, column-name copying, adding columns on either side, and adding or deleting rows. It does not yet implement spreadsheet range selection, fill handles, search, or server/windowed data.

Project conventions

The project follows wigglystuff's useful conventions: a small Python AnyWidget class per experiment, a bundled JavaScript entry point, static assets packaged by Hatchling, a Makefile, and focused Python tests.

AGENTS.md is the Codex-native replacement for wigglystuff's Claude-facing instructions. .codex/workflows.md records the equivalent development commands. Conductor's declarative workspace schema (.conductor/settings.toml) has no Codex equivalent, so it is intentionally not copied; the command mapping is documented in .codex/workflows.md instead.

Development

make install
make build
make test
make dev

To inspect the widget in marimo after installing the package locally, run uv run --with marimo marimo edit demos/exp_data_editor.py.

Releasing to PyPI

Releases use GitHub Actions and PyPI Trusted Publishing, so a PyPI API token is not stored in this repository.

One-time setup:

  1. If lumut is not yet on PyPI, add a pending Trusted Publisher in your PyPI account settings. If it already exists, add a Trusted Publisher in that project's Publishing settings. In both cases use project name lumut, owner banditelol, repository lumut, workflow file publish.yml, and environment pypi.
  2. The first successful run of a pending publisher creates the PyPI project; its configured project name must exactly match project.name.
  3. In GitHub, create the protected pypi environment if you want approvals before publication. The workflow works without protection too.

For each release, update version in pyproject.toml, add release notes, and create a GitHub release whose tag is v<version> (for example, v0.1.0). The publishing workflow rebuilds the frontend, builds and checks the wheel and source distribution, then uploads those exact artifacts to PyPI. To verify a build locally before creating the release, run make package and uvx twine check dist/*.

Metadata

Release files for lumut 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lumut 0.1.1
File Size Uploaded
lumut-0.1.1.tar.gz 233.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lumut 0.1.1
File Interpreter ABI Platform
lumut-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 471.3 kB

Release files / lumut-0.1.1.tar.gz

Download URL lumut-0.1.1.tar.gz
Size 233.9 kB
Tags Source
SHA-256 checksum
How to use checksums
d760ce462f6d5a8dc48a2ffcaad7e3fc8b4ed188bbd4aa4d112f03ca10c60d53
BLAKE2b-256 checksum
How to use checksums
71d966ef02a67fc8f2699b212a481b7bd2d9901d79eac66c508ae49230d3c0df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / lumut-0.1.1-py3-none-any.whl

Download URL lumut-0.1.1-py3-none-any.whl
Size 237.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2857f09b91ddc5a6febae4ad829ac68a9eb88482ff09663d6f6cb684f653a900
BLAKE2b-256 checksum
How to use checksums
bb139356f1097e68d51f466c8b24ae0672d486fc7846f579fce87731cd064a98
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page