Replace examples/ (currently 9 ad-hoc files with overlapping concerns) with a minimal canonical set of 5 progressively-richer, independent showcases that together stress-test the entire shipped violetear surface, while each remains simple enough to read end to end.
Each example is independent — readable in isolation, no shared helper module, no assumption that the previous example has been read. The progression is in capability tier, not in domain or code reuse.
| # | File | Tier | Domain |
|---|---|---|---|
| 01 | 01_static.py | Pure markup + CSS, no server | Design-tokens reference page |
| 02 | 02_ssr.py | Server-only, no Pyodide bundle | Guestbook (GET list + POST add) |
| 03 | 03_interactive.py | SSR + client-side Python, single user | Unit converter (m / ft / in) |
| 04 | 04_pwa.py | Installable + offline | Pomodoro timer |
| 05 | 05_realtime.py | Multi-user via WebSocket | Chat room with presence |
StyleSheet with many .select(...) rules.Style fluent builder (.font, .color, .padding, .background, .border, .rounded, .flexbox).Colors.* named registry.Unit types (px, rem, %, em).Document with head styles, body content tree, HTML.div(...).style(...) chaining, ElementSet.spawn for the swatch grid.Test imports the module, invokes its build function, asserts <!DOCTYPE html> in rendered HTML and a known color hex (e.g. #D97757) in rendered CSS.
@app.view for GET routes.@app.api.post for form-driven mutation — deliberate signal that violetear doesn't yet have a first-class form-POST helper and that's fine for SSR.doc.style(href=..., sheet=...) registering an auto-served stylesheet route.<form> / <input> / <button> via the markup builder.TestClient: GET / returns 200 with the empty-state markup; POST /entries with form data returns 303; subsequent GET shows the new entry.
@app.local with multiple primitive fields (m, ft, in, mode).data-bind-value on the inputs — SSR-rendered, hydrated.@app.client.callback on different events (one per input).@app.client.on("ready") lifecycle.@app.server.rpc with float args + dict return.violetear.storage.store round-trip.violetear.dom.DOM.find.Test asserts SSR markup contains data-bind-value="UiState.meters" etc., the bundle compiles, and POST /_violetear/rpc/precise_convert returns the expected dict.
@app.view with a custom Manifest object.violetear.storage.store written every tick — validates round-trip-on-mutation.asyncio loop on the client (validates Pyodide concurrency).@app.local mutation from a non-callback client function.Test asserts the manifest endpoint serves the expected JSON (name, theme_color, scope = /), the SW endpoint serves a script that lists the bundle URL in its assets, the bundle compiles.
.broadcast(...) (message fan-out) AND .invoke(client_id, ...) (targeted reverse-RPC for history).@app.client.on("connect") — the feature we wired in e32a24b.@app.server.realtime AND @app.client.realtime in the same file.@app.shared will replace once it ships.Test connects two TestClient.websocket_connect sessions concurrently; the second client receives the first's join broadcast; sending a message from client A causes client B to receive a receive_message envelope with the right shape.
examples/0N_<slug>.py — numeric prefix preserves tier order in directory listings.from violetear.dom import DOM inside @app.client.* functions is the established pattern and correct (Pyodide-side imports).if __name__ == "__main__": at the bottom. Tier 1 writes files; tiers 2–5 call app.run() (uvicorn).Add tests/test_examples_canonical.py with one thin smoke test per example. Goal: catch regressions when the framework changes — not to validate the examples' behavior in depth (that's what the example itself demonstrates by running).
Each test:
TestClient(example_module.app.api), exercises one happy-path request, asserts on key markers (right HTML, right manifest, right WS envelope shape).Total cost: roughly 5 small tests, ~150 lines combined.
Delete all 9 in a final commit after the last new example lands:
basic_pwa.py broadcast.py full_pwa.py
hello_world.py quickstart.py reactivity.py
rpc_call.py server_realtime.py simple_client.py
Rationale: a transitional _legacy/ directory adds clutter without value — the git history is the archive. The new set replaces the old completely. Bonus: deleting reactivity.py removes any lingering reference to the class_name= pattern that was the source of the bug surfaced in slice 1 and fixed in 540a354.
Build one example at a time, in numerical order. After each:
make (= make test-unit) is green locally.feat(examples): canonical 0N_<slug> — <one-line description>.After the last example lands, in a final commit:
README.md's quickstart to nudge readers toward the new set.AGENTS.md "Common workflows" to point at the canonical examples.@app.shared example until the feature ships._legacy/ directory.issues/6-...md per repo convention (not the global docs/superpowers/specs/ default)..invoke(client_id, ...) — exercises both directions in one flow.examples/01_static.html). Alternative: examples/_out/. Lean: alongside, for "open it and see" simplicity.@app.shared example. When the feature ships, revise example 5 to use it (replacing the manual broadcast pattern) OR add a 6th example. Decision deferred until the feature lands.markup.py:Component but no example exercises it. Add later if magpie/superbot stresses it.