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.
Gallery
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:
- Glide
rowHeightAPI - Glide issue #581: Make Row Height Fit Content
- Glide releases
- TanStack Virtual dynamic measurement example
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:
- If
lumutis 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 namelumut, ownerbanditelol, repositorylumut, workflow filepublish.yml, and environmentpypi. - The first successful run of a pending publisher creates the PyPI project; its configured project name must exactly match
project.name. - In GitHub, create the protected
pypienvironment 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)
| File | Size | Uploaded | |
|---|---|---|---|
| lumut-0.1.1.tar.gz | 233.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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