Changelog
All notable changes to this project are documented here. The format follows Keep a Changelog and the project uses semantic versioning.
#[0.4.2]
#Added
- VS Code extension: PyWeb: Run App (a run button on
.pywebfiles, Ctrl+F5), New App from any template, Check App, Add npm Package, Install or Update PyWeb and Set Up AI Assistant (MCP), which adds the PyWeb MCP server to.vscode/mcp.json. - VS Code extension: closing tags are added as you type, Emmet works in markup, new snippets (
layout,error,stream,livequery,npm,mount), a status item that shows the PyWeb version in use, and an icon.
#Fixed
- VS Code extension: the language server failed with
spawn python ENOENTwhen no Python interpreter was selected. The extension now looks for a Python with PyWeb (the selected interpreter,pyweb,py,python3,python), offers to install PyWeb when it's missing, and restarts when you change interpreter.
#[0.4.1]
#Added
- MCP server:
pyweb_routesmaps every page (route, path and query parameters with types and defaults, title and head tags, layouts, live-data variables), the layouts, error pages and server functions. - MCP server:
pyweb_packagesadds, removes or lists npm packages for browser code, with each package's exported names and thenpm(...)lines to bind them. - MCP server:
add_ai_featureandmake_data_liveprompts. - MCP server:
pyweb_callreturns every chunk a streaming function yields;pyweb_renderreports the title, head tags and the layouts that rendered;pyweb_checkreports layouts, live variables, npm packages and streaming functions. - The AI guide (
pyweb_guide,AGENTS.md) has sections with working examples for multi-page apps, streaming and AI, live data and npm packages. - A new README with screenshots, and docs on publishing the VS Code extension.
#Fixed
- Markup right after a
defline that ends in a comment (def Page(q: str = ""): # ...) failed to compile.
#[0.4.0]
Apps that are bigger, livelier and smarter: npm packages without Node.js, multi-page apps with layouts and client-side navigation, streaming for AI features, and page data that follows the database.
#Added
- npm packages without Node.js.
pyweb add chart.js/autodownloads a package from the npm registry, checks its sha512 checksum, follows its imports from the browser entry point and copies only the files it needs (and its dependencies) intostatic/vendor/, pinning versions inpyweb.lock.pyweb addwith no arguments reinstalls from the lock;pyweb removetakes a package out. Browser code binds exports withChart = npm("chart.js/auto")ornpm("pkg", "Export"). Calling a class constructs it and keyword arguments become an options object. Each page imports only the packages it uses, through an import map that the Content Security Policy allows by hash. CommonJS-only and Node.js-only packages are refused with an explanation, and an uninstalled package is a compile error that names the command to run. ref={el}sets a page variable to an element, in time foron_mount, for libraries that draw into the page.- Changing an attribute of a package's object (
chart.value = 5) re-renders whatever shows it. - Web components (custom elements from npm) work in markup.
pyweb lsp: hover shows an npm binding's installed version and TypeScript signature; completion offers installed packages innpm("and a module's exports aftername..- New docs page: npm packages.
- Layouts.
@app.layoutwraps every page (or, with@app.layout("/admin"), pages under a prefix) in shared markup;{children}marks where the page goes. Layouts nest, have their own server code, state and handlers, and pages can opt out withlayout=Noneor pick one withlayout="Name". - Client-side navigation. Links between pages fetch the next page's HTML and swap it in below the layouts both pages share, so layout state (an open menu, a player) survives. Links are prefetched on hover or focus, back/forward restore the scroll position, and anything unusual falls back to a full page load.
navigate("/path")frompyweb.browserdoes the same from browser code;on_unmountruns when a page is left;App(client_nav=False)turns it off. - Current links. Links to the current page get
aria-current="page"and links to its parent sectionsaria-current="true", on the server and after each navigation. - Head tags.
@app.page(description=..., image=..., noindex=...)andhead(title=..., description=...)in page code add description, Open Graph and Twitter tags;App(base_url=...)adds canonical URLs and absolute image links. - Error pages in
.pyweb.@app.error(404)/@app.error(500)pages takepath,status,messageorrequest_idand use root layouts. - Typed query parameters. Page parameters that aren't in the route are read from the query string and converted by annotation (
int,float,bool,list[...]); bad or missing values answer 400. - New docs page: Layouts & navigation.
- Streaming server functions. A
@serverfunction thatyields sends each value as it's produced (NDJSON over the same RPC endpoint); browser code reads it withasync for.stream.cancel()or leaving the loop aborts the request and closes the generator on the server, so an upstream AI call stops too. Errors raised part-way arrive asRPCError. Works withasync defgenerators, underpyweb serve,pyweb devand ASGI, andTestClient.rpcreturns the streamed values. <Markdown text={...} />(frompyweb) renders Markdown to safe HTML on the server and updates it in the browser as the text changes, with the same rules in both places. Raw HTML is shown as text and links only accept http(s), mailto and relative URLs.ai-chattemplate and example: a streaming chat with Stop and Markdown replies that talks to Anthropic, any OpenAI-compatible server (OpenAI, Ollama, vLLM, ...) or a built-in demo model, chosen by environment variables.pyweb new NAME --template ai-chat.- New docs page: Building AI apps.
- Live data.
rows = live(db, "select ...", params)in a page or layout renders the rows and keeps them current: writes throughpyweb.db(andpyweb.models) announce their table after commit, each distinct query re-runs once per change (bursts coalesced) and sends rows to every page showing it only when they changed. Pages follow a signed feed;db.notify("table")covers writes made elsewhere. WithRedisBusit works across processes, and any process can take over a query from the page's signed description. - New docs page: Live data.
watch(lambda: value, handler)(frompyweb.browser) runs a handler whenever a value changes, for work outside markup such as redrawing a chart or saving a draft.pyweb addapplies packages'browserfield (browser versions of files, modules turned off), so packages such as ethers install.- New examples: dashboard (live queries and a Chart.js chart from npm), site (layouts, navigation, query parameters, a 404 page) and ai-chat. The playground has the site and AI chat examples.
- New docs page: Recipe: wallets & web3 (wallet sign-in with ethers and eth-account).
- A layout's
{children}can sit on a line of its own.
#Fixed
- Subscriptions and cleanups set up in
on_mountnow end when the page is left (they were never stopped).
#Changed
- The experimental
pyweb.livemodule (in-memory live tables) is nowpyweb.livetable;pyweb.liveis the new live-query function. pyweb.npm(TypeScript declarations to dataclasses) is nowpyweb.dts, and its command ispyweb dts.pyweb buildno longer writes an esm.sh import map; packages come frompyweb.lock.- The default Content Security Policy adds
worker-src 'self' blob:so packages can start Web Workers. - The starter stylesheet styles Markdown and chat bubbles.
- The browser runtime is about 14 KB gzipped (was about 11 KB), for client-side navigation and npm support.
- New website design: a colour system where blue means the browser and amber the server, self-hosted Inter / Bricolage Grotesque / JetBrains Mono, dark code windows, and a landing page that shows each line of an app with where it runs (worked out by the compiler) next to the real compiled output.
- The example apps and the
pyweb newstylesheet use the new palette.
#[0.3.0]
Faster pages that feel like an app, apps that span several files, and tools for editors, the browser and AI assistants.
#Added
- Hydration. Pages now adopt the server-rendered DOM instead of rebuilding it: nodes are kept, focus and text typed before the script loads survive, and typed values reach their variables. When the server HTML doesn't match, the page falls back to a client render and logs a warning. The page root carries
data-pw-mode="hydrated"or"rendered". - Live updates.
publish(name, data)in server code pushes to every page that calledsubscribe(channel_feed, handler), over Server-Sent Events with a polling fallback. Feeds come fromchannel(name)while rendering: signed, expiring, and positioned so no message published after the render is missed.pyweb dev,pyweb serveand the ASGI adapter stream;realtime.use_bus()shares the bus (e.g. Redis) between processes. The chat example uses it instead of polling. - Multi-file apps.
from widgets import Card, save, COLORimports components,@serverfunctions and constants fromwidgets.pyweb(orui/cards.pywebasui.cards). Each file keeps its own names in the generated JavaScript, imported files run as real modules on the server,pyweb buildcopies them intodist/, and errors name the file they're in. Pages stay in the app file; circular imports and duplicate server-function names are compile errors. - Editor support.
pyweb lspis a language server (stdio, standard library only): compile errors and security warnings as you type, hover showing where each name runs and why, completion for components, props and tags, go to definition across files, and an outline. The new VS Code extension (editors/vscode) adds highlighting and snippets and starts it; releases attach a.vsix. Setup for Neovim and Helix is in the command-line docs. - Playground on the website: edit an app and run it in the browser, with the real compiler, server rendering,
@serverfunctions, sessions and live updates running on Pyodide. Shows the compiled JavaScript and what runs where, reports errors with a fix, and shares code as links. - MCP tools
pyweb_screenshot(open a page in headless Chromium, run click/fill/press steps, get a screenshot, the page text, console errors and hydration status) andpyweb_test(run the app's pytest tests and report failures). New apps include a startertest_app.py. python -m pyweb.bench --max-ssr-ms N --max-compile-ms Nfails when a benchmark exceeds its budget; CI runs it.
#Removed
- The old
pyweb.lsphelper functions (complete,DocumentState,complete_*,diagnostics_for_compile_error,hover(compiled, name),boundary_lens); they described an earlier API and no editor used them.
#Changed
import pywebno longer needs thesqlite3module (it's imported when a SQLite database is opened), and password hashing falls back to a pure-Python PBKDF2 on Pythons without OpenSSL. Both make PyWeb run on Pyodide./__pyweb/eventsand/__pyweb/pollrequire a signed?feed=; the unauthenticated?channel=form is gone, so channels are no longer readable by anyone who guesses a name.- Server rendering caches compiled template expressions; rendering a page is about twice as fast.
- Generated page modules pass element children as a function and wrap every child in a call (
$t,$dyn), so nodes are created in document order.
#[0.2.0]
Tools for building PyWeb apps with AI assistants.
#Added
pyweb mcp: a Model Context Protocol server (stdio, standard library only) for Claude Code, Cursor, Claude Desktop, VS Code and other MCP clients. Tools:pyweb_guide,pyweb_new_app,pyweb_check(errors with line numbers and fix hints),pyweb_inspect,pyweb_compiled,pyweb_renderandpyweb_call(session cookies persist between calls); resources for the guide and templates; abuild_pyweb_appprompt. Tested against the official MCP Python SDK.- An AI-oriented guide to writing
.pywebapps, shipped in the package. pyweb new --template blank|counter|todo|blog|auth|chat; new projects includeAGENTS.mdandCLAUDE.mdfor coding agents.- The docs site publishes
llms.txtandllms-full.txt, and has a new "AI assistants & MCP" page.
#[0.1.0]
The first public release, published on PyPI as pyweb-stack. The unpublished prototype was labelled "1.0" but only the simplest counter pattern worked in a browser; this release rebuilds the compiler, runtime and server around a design that works end to end, and is verified in a real browser.
#Language and compiler
.pywebparser with a character scanner: tags and expressions may span lines, braces and strings nest inside{...},elifchains, HTML comments, and exact line numbers (the Python half keeps the source's line count; this also fixes crashes on Python 3.10/3.11).- Python → JavaScript translation of a documented subset with Python semantics (truthiness,
==on containers, negative indexing, floor division/modulo, string/list/dict/set methods, Python exceptions). Unsupported code is a compile error withfile:line. Verified differentially against CPython. - State inference: signals (mutated by handlers or bound), computeds, constants; initial values from literals, the browser, or the server. Mutations (
append, item assignment,del, …) are copy-on-write. - Components with props, defaults and
children;on_mounthook; page titles;App(title=, stylesheets=, lang=). @servercalls from handlers compile to awaited typed RPC; async propagates through handler calls.- Only values browser code reads are serialised; values derived from server data are computed on the server. Secret-looking names read by browser code are rejected.
#Runtime
- New browser runtime: dependency-tracked signals, cached computeds, owned effects with disposal, batching; keyed lists and conditionals in marker-bounded regions; two-way binding for text, number, checkbox, radio and select;
javascript:URL blocking; RPC client with typed errors.
#Server
- Pages render per request with real server values;
request,session,redirect,NotFound; typed route parameters. @serverfunctions are registered automatically indev,serve, tests and ASGI.- New ASGI adapter:
pyweb.asgi.create_app. - RPC: JSON-only and same-origin checks, rate limiting, timeouts that actually return on time, async functions, JSON conversion for dataclasses/datetimes/Decimals.
- Content-Security-Policy and security headers on HTML responses.
#Tooling
pyweb dev: live reload and an in-browser compile-error overlay.pyweb buildproduces a self-containeddist/(source, manifest with gzip sizes, hashed assets, working Dockerfile);pyweb serveruns it.- Token-aware JS minifier (the previous one altered string literals).
pyweb.testing.TestClientandpyweb.testing.servefor tests.- Clean
file:line: messagecompile errors from the CLI.
#Data, auth, jobs
?placeholders work on Postgres and MySQL; lazy connection pools.- WebAuthn: challenge, origin and sign-count checks; DER signatures; uses
cryptographywhen installed. - Redis bus and queue work against real Redis.
#Fixed
- Migrations: the
pyweb_migrationsjournal table can now be created on MySQL (VARCHAR(255)key instead ofTEXT). pyweb servecrashed on startup (logger misconfiguration).- Production builds referenced a hashed runtime the page modules never imported.
- Installed packages were missing the browser runtime file.
transaction()did not roll back on Postgres/MySQL pools.sqlite:///file.dbpointed at the filesystem root.RedisBus.since()duplicated and mis-ordered messages;RedisQueue.drain(timeout=0)blocked forever.- Jobs raising
TypeErrorran twice. Query.order_by()ignored-fieldand did not validate a single field.- The deploy Dockerfile ran a non-existent
serve --dirflag.
#Removed
- Modules the framework no longer uses:
pyweb.components(pure-Python element API),pyweb.routing(unused router),pyweb.reactive(server-sideSignal/Computed/Effect/live), andpyweb.compiler.placement(superseded by the compiler's boundary checks). - The
@browserand@shareddecorators, which had no effect. pyweb.compiler.rpc.client_stub/server_handlerand unused AST classes.docs/BUGLOG.md(it described the prototype's internals) and the committedwebsite/dist/; the Pages workflow builds the site.- The regex-based codegen, the virtual test client that only recorded clicks, documentation "snippets" that did not use PyWeb, island and streaming-SSR string helpers without runtime support, and the no-op
--security-scanflag.
#Pre-release prototype (unpublished)
- Initial prototype.