Skip to content

Development

oldmangan edited this page Oct 11, 2026 · 7 revisions

Layout of the repository

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.

Running

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_demo

Asset manifest

pyTincture 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 runs

CI 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.)

Two pull requests that both change assets

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 push

CI 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.

Adding a widget

  1. wapyt/<widget>/<widget>_config.py: @dataclass config(s) with to_dict() returning camelCase keys, dropping None so JS defaults win, and an extra: dict passthrough.
  2. wapyt/<widget>/<widget>.py: the wrapper. Take config, *, container=None, root=None; call require_js("<Widget>"); expose on_* methods that convert payloads with to_py() and keep their proxies.
  3. wapyt/assets/<widget>.js: an IIFE registering globalThis.wapyt.<Widget>, with on(event, fn) and the methods the wrapper calls. Use textContent for text and escapeHtml() on anything interpolated into innerHTML. Styles go in wapyt.css using the --wapyt-* tokens and the [data-wapyt-theme="dark"] selector.
  4. Add the JS file to the ASSETS list in scripts/generate_assets_manifest.py (after icons.js), then regenerate the manifest.
  5. Export the classes from wapyt/__init__.py and __all__; add Layout.add_<widget>(id, <widget>_config) if it mounts into a cell.
  6. Unit-test to_dict() in tests/test_new_widget_configs.py.
  7. Try it in a demo or a real app through dev_wheel.sh.
  8. Update the wiki: the Widget Catalog, a widget page, the sidebar, and move its Roadmap entry.

Icons

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's two classes

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.

Continuous integration

.github/workflows/ci.yml runs on every push to main and every pull request:

  1. the unit tests;
  2. node --check on every file in wapyt/assets/;
  3. the asset manifest --check;
  4. uv build;
  5. 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 besides wapyt and its dist-info (until #66 the wheel also installed tests and scripts packages).

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_TOKEN repository 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" as github-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.

Versioning and releasing

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):

  1. Set the version in all three places (for a final release, drop the .devN), run python3 scripts/generate_assets_manifest.py ., open a PR and merge it.

  2. Tag main and push the tag:

    git tag -a v0.2.0 -m "wapyt 0.2.0" && git push origin v0.2.0
  3. .github/workflows/release.yml checks the tag equals all three versions, re-runs the CI checks, and builds and verifies the files.

  4. 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 in aN, bN, rcN or .devN are 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 wapyt project's owners are Stephen and el-iguana26; its trusted publisher is owner WAwesome-AI, repository wa_pytincture_widgetset, workflow release.yml, environment pypi. (wapyt 0.1.0 was published in December 2025 from the original schapman1974/wA_pytincture_widgetset repository.)
  • GitHub: the pypi environment has a required reviewer and only accepts v* tags, so nothing publishes without a release tag and an approval.
  • TestPyPI cannot be used to rehearse: the name wapyt there belongs to an unrelated project. Rehearse with a .devN tag 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.

Contributing

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.

Clone this wiki locally