Repository navigation
Development
wapyt/
__init__.py # __widgetset__ / __version__ and the public exports
_runtime.py # Pyodide bridge: require_js, create_proxy, offline asset fallback
<widget>/
<widget>.py # thin Python wrapper over the JS constructor
<widget>_config.py # @dataclass config; to_dict() emits camelCase
assets/ # the JS/CSS that renders: one file per widget + wapyt.css
icons.js # icon resolution onto MDI; must load before sidebar.js and chat.js
pytincture-assets.json # hashed asset manifest, generated
scripts/
dev_wheel.sh # build the browser wheel into an app's modules folder
generate_assets_manifest.py # regenerate / --check the manifest
check_dist.py # verify a built wheel and sdist like pyTincture does
.github/workflows/
ci.yml # every push to main and every pull request
release.yml # a v* tag publishes to PyPI
tests/
test_*.py # CPython unit tests for the Python half
*_demo.py # small pyTincture demo apps
Every widget follows the same path: config dataclass → to_dict() →
json.dumps → js.JSON.parse → js.wapyt.<Widget>.new(root, payload), with
events returning through create_proxy.
uv run --group dev pytest tests/ # unit tests, no browser needed
./scripts/dev_wheel.sh tests # demos: build the wheel into tests/ first
cd tests && uv run layout_demo.py # http://localhost:8070/layout_demopyTincture hash-verifies every widget JS/CSS file against
wapyt/pytincture-assets.json, which also records the wapyt version, and
evaluates the files in manifest order. After any asset edit or version
change, regenerate it and commit the JSON:
python3 scripts/generate_assets_manifest.py . # regenerate
python3 scripts/generate_assets_manifest.py . --check # what CI runsCI fails a pull request whose manifest is out of date. (Before CI existed the
manifest on main had gone stale across many PRs, which would have made a
PyPI wheel unusable; #47 fixed it.)
The manifest holds one hash per file, so two open PRs that both touch
wapyt/assets/ (often both editing wapyt.css) cannot both be right. Once
one merges and the other is updated from main, the second one's manifest
is stale and CI fails (#49 did, after
#50). Regenerate on the updated branch and push:
git pull # the branch, now including main
python3 scripts/generate_assets_manifest.py .
git commit -am "Regenerate the asset manifest" && git pushCI can do this itself if the repository has a MANIFEST_BOT_TOKEN secret;
see Continuous integration. It has none at the
moment, so this step is manual.
Keep icons.js first among the scripts: sidebar.js and chat.js call
wapyt.icons while rendering. Otherwise each widget's JS touches only its own
globalThis.wapyt.* entry.
-
wapyt/<widget>/<widget>_config.py:@dataclassconfig(s) withto_dict()returning camelCase keys, droppingNoneso JS defaults win, and anextra: dictpassthrough. -
wapyt/<widget>/<widget>.py: the wrapper. Takeconfig, *, container=None, root=None; callrequire_js("<Widget>"); exposeon_*methods that convert payloads withto_py()and keep their proxies. -
wapyt/assets/<widget>.js: an IIFE registeringglobalThis.wapyt.<Widget>, withon(event, fn)and the methods the wrapper calls. UsetextContentfor text andescapeHtml()on anything interpolated intoinnerHTML. Styles go inwapyt.cssusing the--wapyt-*tokens and the[data-wapyt-theme="dark"]selector. - Add the JS file to the
ASSETSlist inscripts/generate_assets_manifest.py(aftericons.js), then regenerate the manifest. - Export the classes from
wapyt/__init__.pyand__all__; addLayout.add_<widget>(id, <widget>_config)if it mounts into a cell. - Unit-test
to_dict()intests/test_new_widget_configs.py. - Try it in a demo or a real app through
dev_wheel.sh. - Update the wiki: the Widget Catalog, a widget page, the sidebar, and move its Roadmap entry.
Add Material Symbols name mappings as LIGATURE_TO_MDI entries in
assets/icons.js, never a font <link>. Check each MDI class exists in
pyTincture's vendored stylesheet, since a missing one renders nothing. The repo's
CLAUDE.md has the one-line check.
chat.js defines WapytChatApp (the implementation) and ChatWidget (the
exported facade on globalThis.wapyt). Methods do not proxy automatically: a
method added only to WapytChatApp is invisible to Python. Events must go out
through this.host.emit(...), because the Python wrapper listens on
ChatWidget's bus.
.github/workflows/ci.yml runs on every push to main and every pull
request:
- the unit tests;
-
node --checkon every file inwapyt/assets/; - the asset manifest
--check; -
uv build; -
scripts/check_dist.py dist, which opens the wheel and the sdist and checks them the way pyTincture will: the manifest names the right version, every asset's SHA-256 matches, and the wheel declares no runtime dependencies (micropip would try to resolve them in the browser), and that it installs nothing at the top level besideswapytand its dist-info (until #66 the wheel also installedtestsandscriptspackages).
On pull requests a manifest job runs first (#51). If the manifest is
current, the steps above run as usual. If it is stale:
-
With the
MANIFEST_BOT_TOKENrepository secret set (a fine-grained token for this repository only, Contents: read and write), and the PR coming from a branch in this repository, CI regenerates the manifest, commits "Regenerate the asset manifest" asgithub-actions[bot]to the branch and pushes it. That push starts a new run that tests the fixed commit. Pull before pushing more work to that branch. - Otherwise (no secret, which is the case today, or a PR from a fork) the job fails with the command to run.
The secret is needed because a push made with the built-in GITHUB_TOKEN
doesn't work as a check: on a test PR, GitHub held the run it started for
approval, and a run started another way wasn't shown on the PR.
The version lives in three places that must agree: pyproject.toml,
__version__ in wapyt/__init__.py (a plain string literal, because
pyTincture reads it by parsing the file) and the asset manifest.
tests/test_release_metadata.py checks they do.
main is 0.2.0, released on 2026-10-11 and the version a plain
pip install wapyt now installs. The Roadmap's remaining parity
items and the Scheduler's resource timeline follow in later versions.
To release (a maintainer):
-
Set the version in all three places (for a final release, drop the
.devN), runpython3 scripts/generate_assets_manifest.py ., open a PR and merge it. -
Tag
mainand push the tag:git tag -a v0.2.0 -m "wapyt 0.2.0" && git push origin v0.2.0
-
.github/workflows/release.ymlchecks the tag equals all three versions, re-runs the CI checks, and builds and verifies the files. -
The publish step then waits for approval: in the Actions run, click Review deployments, tick
pypi, Approve and deploy. It publishes to PyPI and creates a GitHub release with the files. Versions ending inaN,bN,rcNor.devNare marked pre-release, and pip installs them only with--pre(or the exact version).
Publishing uses PyPI trusted publishing: no API token is stored anywhere.
Set up (done 2026-10-08):
- PyPI: the
wapytproject's owners are Stephen andel-iguana26; its trusted publisher is ownerWAwesome-AI, repositorywa_pytincture_widgetset, workflowrelease.yml, environmentpypi. (wapyt 0.1.0 was published in December 2025 from the originalschapman1974/wA_pytincture_widgetsetrepository.) - GitHub: the
pypienvironment has a required reviewer and only acceptsv*tags, so nothing publishes without a release tag and an approval. - TestPyPI cannot be used to rehearse: the name
wapytthere belongs to an unrelated project. Rehearse with a.devNtag on PyPI instead.
Releases from this repository:
| Version | Date | Notes |
|---|---|---|
| 0.2.0 | 2026-10-11 | The first final release from this repository (#74, GitHub release). The same code as 0.2.0.dev2. |
| 0.2.0.dev2 | 2026-10-11 | Adds the MediaPlayer (#70, #71, #72) and the iCal warning fix (#69); version set in #73 (GitHub release). |
| 0.2.0.dev1 | 2026-10-08 | Trial run of the pipeline (#68, GitHub release). Everything through the Scheduler. Prints a harmless SyntaxWarning on import from the iCal code, fixed after it in #69. |
Work on a feature branch and open a pull request against main; CI must pass.
Apps pin wapyt by commit (WAPYT_REF in their Containerfiles), so a change
reaches them only when they bump that pin. Moving an app to 0.2.x also means
updating its widget.py version and any pinned or vendored 0.1.0 wheel.
wapyt is MIT-licensed · Repository · pyTincture · Issues
Getting started
Widgets
- Layout and MainWindow
- Tree
- DataTable
- Form
- ModalWindow
- Window
- TabWidget
- Toolbar
- Sidebar
- Chat
- Terminal
- CardPanel
- ResourceBoard
- File Transfer
- Message
- ContextMenu
- MenuBar
- Listbox
- Kanban
- ProgressBar
- Pagination
- Popup and tooltips
- Chart
- Scheduler
- MediaPlayer
Guides
Planning
Contributing