Changelog
Release notes for Pyxle. While we're in beta (0.x), minor versions may include breaking changes — those are called out explicitly. To upgrade, run pip install --upgrade pyxle-framework.
0.7.5
pyxle devnow persists module-level state across requests, likepyxle serve. A@server/@actionmodule is imported once and reused between rebuilds, so a module-level counter or in-memory cache no longer resets on every refresh in dev. Saving a file still re-imports it (resetting globals and applying your edits). State stays per-process — use a database or cache for anything shared or durable (Loaders should be stateless).pyxle initnow requires an explicit target. A barepyxle initused to silently scaffold into the current directory; it now errors, pointing topyxle init my-app(new directory) orpyxle init .(current directory).
0.7.4
- Fix:
pyxle devno longer hot-reloads in an endless loop on Linux. After a save, the rebuild's own file reads surfaced throughinotifyas events the watcher mistook for edits, re-triggering the rebuild forever (macOSFSEventsdoesn't report reads, so it was Linux-only). The watcher now reacts only to genuine change events, not read-only opens.
0.7.3
- Docs: an
@action's awaited result is the flat{ ok, ...yourReturn }— read fields directly (res.title), neverres.data.title(a successful result has no.data;useAction().datais a separate hook property). Clarified across the Server Actions guide, the client API reference, and the scaffoldAGENTS.md.
0.7.2
- Fix:
pyxle initscaffolds an installablerequirements.txtagain. The template'sstarlettepin conflicted with the framework's after 0.7.1's security bump; both now usestarlette>=1.3.1,<2.0. - Config: a boolean
portis now rejected instead of binding port 1.boolbeing anintsubclass slipped past the validator; it now raises a clearConfigError.
0.7.1
- Security: require Starlette ≥ 1.3.1, fixing a
Host-header auth bypass (PYSEC-2026-161), form-parsing DoS (PYSEC-2026-249, -1943), and a WindowsStaticFilesSSRF (CVE-2026-48818). Pyxle's own API is unchanged; a newpip-auditCI job guards dependencies. pyxle serverefuses to start in production withoutPYXLE_SECRET_KEY— previously only a warning, leaving CSRF tokens and signed cookies forgeable. Security, Deployment.pyxle dev/buildcheck the Node.js version up front — a clear "Node.js 20.19+ required" message instead of an opaque Vite crash. Installation → Troubleshooting.- A real testing story:
pyxle.testinghelpers and a Testing guide.load_loader("pages/index.pyxl")/load_page(...)compile a page and hand back its loader/module for unit tests. - New Debugging guide and Deployment fixes — Node 22 in the sample Dockerfile, the real metrics path (
/api/__pyxle/metrics), a migrations section, and a blue-green CSRF gotcha. - Security docs now match what Pyxle sends — it already sets
X-Content-Type-Options/X-Frame-Options/Referrer-Policy, so the guide covers only CSP and HSTS; a SECURITY.md is published for every repo. - A fresh project now ships a human
README.mdalongside the AI-orientedAGENTS.md. - The comparison guide names current gaps — TSX authoring, a first-class test client, automatic image/font optimization, i18n — with how to bridge each today.
0.7.0
- Fix:
error.pyxlrenders even when an ancestor layout has a@serverloader. Boundary renders now run ancestor layout loaders like a normal page instead of silently falling back to the built-in error document. - Pre-release fixes.
PYXLE_SSR_WORKER_CONCURRENCYreaches the worker again,SIGTERMtopyxle devtears down the whole child tree, a failed rebuild broadcasts to the overlay, and reverting to last-good content triggers the recovery rebuild. - Fix: CommonJS React libraries no longer crash SSR. The SSR bundler resolves dependencies ESM-first (like Vite), so packages without an
exportsmap (e.g.lucide-react) link cleanly; a genuinely CJS-only package now fails with an actionable error. Third-party packages. - Docs: the road to a fully-typed Pyxle — where the type story is heading (TSX authoring, Python types across the boundary), grounded in what works today.
- Breaking: Node.js 20.19+ is now required (Vite 7's floor; Node 18 is EOL). Installation.
- Modernized scaffold — React 19, Vite 7, and an interactive
pyxle init. Arrow-key prompts for Tailwind, shadcn/ui, and the import alias, with--yes/explicit flags for non-interactive use. Quick Start. - Tailwind is now opt-in — Tailwind v4 wired into Vite when chosen. No config files or standalone watcher; decline it and plain CSS / CSS Modules work out of the box. Styling.
- shadcn/ui support — choose it at
pyxle initandnpx shadcn@latest add …works with noshadcn init; the@alias resolves in the client build and SSR. Third-party packages. - Fix: streaming SSR no longer serializes across requests. An SSR worker now renders many requests concurrently (isolated per-request with
AsyncLocalStorage), andpyxle serveauto-sizes its worker pool. Streaming. pyxle initrenders the framework pin from the running version instead of a stale>=0.4.1that downgraded fresh projects.pyxle typecheckfails fast when TypeScript isn't installed, with an actionable message instead of npm's placeholdertsc. TypeScript.- Scaffolded
AGENTS.mdcorrected —LoaderError(likeserver/action/ActionError/…) is compiler-injected, not imported. pyxle checkworks out of the box —pyxle-langkitis now a default dependency, so JSX checking passes on a fresh scaffold with no extra step.- Fix: the CSRF cookie no longer collides between apps on one host. The default name is namespaced by bind port (
pyxle-csrf-8000) and injected into the page shell. Security → CSRF. - Fix: multipart forms (file uploads) now pass CSRF with a
_csrf_tokenfield. The middleware stream-parses just far enough to read the token (capped at 1 MiB) and replays the body untouched. Security → Form bodies and uploads. pyxle devoutput is clean by default,--verbosefor the firehose — a curated startup summary and one-line rebuild notices instead of Vite's raw stdout. CLI.- Server-side logs stream to the browser console in
pyxle dev, prefixed[pyxle:server]; dev-only and bounded. CLI. public/changes are picked up on refresh instead of triggering a rebuild (matching Next.js) — assets serve live from disk, and a newly added or removed file refreshes the static index. Architecture → the watcher.- Watch extra directories with
dev.watch, ignore paths withdev.ignore. A shared module outsidepages/can now trigger hot reload;dev.ignoreis additive to the built-in ignores. Configuration → Development.
Documentation
- Fixed the flagship WebSockets example — it derived the socket path from
windowduring SSR and 500'd; it now builds the path from loader data. - More doc fixes — a broken
@actionexample in the pyxle-db docs (await request.json()), documented CSRF on the pyxle-auth endpoints and where pyxle-auth accounts live, an honestpyxle checkscope note, and prerequisites up front in the Introduction. Accept: text/markdownnegotiation now follows RFC 9110 (q-values honoured, exact type match); newmarkdown_is_acceptable(accept)helper. AI accessibility./llms.txtnow emits absolute URLs and links.mdonly where Markdown actually resolves. AI accessibility.- Breaking: converted Markdown rewrites internal links to
.md—html_to_markdown()rewrites by default (rewrite_links=Falsefor the old behavior). AI accessibility. - Fix: a burst of rapid saves can no longer kill the dev server — builds are serialized,
meta.jsonwrites atomically, and the Vite subprocess is supervised with bounded backoff. - Fix: reading an unprovided
request.state.<name>(e.g.request.state.dbwithout pyxle-db) now gives a structured, guided error instead of a bareAttributeError. - Actionable SSR error when a component touches a browser global (
window,document, …) — dev names your.pyxlfile and the fix (useEffect/<ClientOnly>); production stays generic. Client Components.
0.6.1
A sharper pyxle check — the edit → check → fix loop now catches classes of mistake it used to wave through.
pyxle checkgained a semantic layer — pyflakes over the Python section flags undefined names, unused imports, and redefinitions (compiler-injected names are recognized).- Duplicate
export defaultis now caught at check time, at the real source line, instead of failing later in the build. - JSX error lines are now accurate, and Babel's misleading "unterminated regex" carries a plain-language hint about the real cause.
LoaderErrorandinvalidate_routesare now auto-injected, matchingActionError/ValidationActionError. Runtime API.pyxle install --break-system-packagesfor externally-managed (PEP 668) environments. CLI.
0.6.0
- AI accessibility — serve your app as Markdown, plus
llms.txt. Opt in with"llms": trueand every page gains a.mdrendition (andAccept: text/markdownsupport), an/llms.txtindex, and discovery headers; Markdown resolves from a co-located<page>.md, ato_markdownhandler, or an ancestorllms.py, with anautoConvertHTML→Markdown fallback. Off by default, and adds nothing to the page hot path. AI accessibility.
0.5.0
The depth release: caching/SSG/ISR, streaming SSR, realtime (WebSockets + a cross-worker Redis broker), Pydantic-validated actions, observability, background work, image optimization, and multi-worker serving — built, documented, and dogfooded on pyxle.dev. The two behavior changes below are called out explicitly.
- Cross-worker realtime — a Redis pub/sub broker. WebSocket channels span worker processes and machines with
PYXLE_REALTIME_BROKER=redis; the in-process broker stays the default. No code change. WebSockets. - Fix: streaming SSR now survives production gzip. A streaming-aware
StreamingGZipMiddlewareflushes the compressor per chunk so the shell reaches the browser first. Streaming. - Fix: dynamic pages are no longer client-nav-cached with stale data. The navigation-cache TTL now mirrors server cacheability, so a dynamic page always refetches on back/forward. Caching.
- Fix:
pyxle build --staticpre-renders pages whose loaders use a plugin. The static builder stands up the same plugin context a request sees, so a DB-backed loader runs at build time. Caching. - Changed (behavior): stricter nested-config validation. The
cors/csrf/observability/… blocks now reject unknown keys at boot, so a typo'd security key fails loudly instead of silently no-opping. Configuration. - Fix:
error.pyxlno longer leaks internal error details in production — the boundary gets a generic message and sanitized type; author-raisedLoaderError/ActionErrormessages still pass through. Error Handling. - Fix:
import.meta.env.PYXLE_PUBLIC_*is now substituted during SSR, so a public env var no longer causes a hydration mismatch. Environment variables. - Fix: scaffold
.gitignoreno longer ignores.env, matching the env-vars doc. - Fix:
<Image>emits a lowercasefetchpriorityattribute (React 18.3.1 rejected the camelCase form). - Fix: production gzip no longer prints
I/O operation on closed file— the compressor closes deterministically on every path. - Fix: scaffolded
jsconfig.jsondrops the deprecatedbaseUrlin favor of tsconfig-relativepaths. - Fix:
pyxle typecheckworks on current TypeScript —"bundler"resolution, nobaseUrl. - Startup warning when a
BaseHTTPMiddlewareis paired with streaming routes (it buffers responses, breaking streaming SSR). Middleware. - Clear compile-time error for TypeScript syntax in a client block (it's plain JSX), pointing at your
.pyxlsource line. TypeScript. - New guides: TypeScript and Migrating from Flask or Django.
error.pyxlis now a client-side error boundary too — a render fault after hydration renders the nearest boundary instead of a blank screen, with the sameerrorprop on both sides. Error Handling.- Built-in rate limiting —
pyxle.middleware.RateLimitMiddleware. A dependency-free token-bucket limiter configured frompyxle.config.json; per-process, so rate-limit at the proxy for one global cap. Off by default. Middleware. - Route policies now apply to
@actionendpoints viarouteMiddleware.actions, closing a bypass where an auth policy wrapped pages but not actions. Middleware. pyxle serve --workers 0auto-detects the core count. The Deployment guide adds a per-worker-state table and rolling-deploy guidance.- Build optimization: responsive
<Image>, modulepreload hints, and--analyze.<Image>emits a responsivesrcsetvia aloaderand gainsfill/sizes/priority; the SSR shell preloads entry chunks;pyxle build --analyzereports bundle sizes. Build Optimization. - Background tasks & deferred work.
request.state.background.add_task(...)(or a{"background": [...]}return) runs after the response;pyxle.tasks.enqueue(...)schedules fire-and-forget work on an in-process pool (per-process — hand off to Celery/ARQ/Dramatiq for durability). Background Tasks. - OpenTelemetry tracing (opt-in). Request/SSR/loader/action spans via the
[observability-otel]extra; fully off and zero-cost by default. Observability. pyxle dev --dashboard— a live terminal observability panel (throughput, error rate, latency, cache hit ratio); dependency-free and dev-only. Observability.- Structured access logging — one line per request (
method/path/status/duration_ms/correlation id) in console or JSON viaobservability.accessLog. Observability. - Observability: request IDs, timing, metrics, and richer health probes. Every request gets a correlation id (
X-Request-Id) and timing; opt-in Prometheus metrics;/readyzruns dependency checks. Metrics are per-worker. Observability. - Typed
@actionrequest validation with Pydantic. Annotate abody: Modelparameter and Pyxle validates before the action runs, returning422with afieldsmap on failure;pyxle openapigenerates an OpenAPI 3.1 document from your models. Optional[pydantic]extra. Server Actions. - Fix: a server module that fails to import is no longer cached as a broken empty module — the real error re-raises on each request.
- WebSockets — page handlers, a client hook, and pub/sub. A page can export
async def websocket(ws);pyxle.realtimeaddschannel/room.publish, WS auth/origin helpers, and auseWebSocket()hook. The in-process broker is per-worker (a Redis broker drops in). WebSockets. - Streaming SSR for
<Suspense>pages. The shell flushes immediately and each boundary streams in as it resolves; opt-in, zero-config, dynamic pages only. Streaming. loading.pyxlroute-level loading states wrap a route in<Suspense>, streamed as the shell and applied identically on the client. Streaming.- Server-side page caching with incremental regeneration. Return
{"data", "revalidate": N}(or aCACHEdirective) to cache rendered HTML;pyxle build --staticwarms it; stale-while-revalidate, strongETag, andcache.invalidate(...). In-memory/disk/Redis backends. Caching.
0.4.5
- Signed cookies & tokens —
sign_cookie/verify_cookie. Stdlib-only helpers that attach a tamper-proof HMAC-SHA256 signature to any string (session id, unsubscribe link, reset token) and verify it in constant time; asalt=namespaces signatures, and signing without a secret fails closed. Security. - Fix:
PYXLE_PUBLIC_*client env vars now work inpyxle devand no longer breakpyxle build— values are emitted as quoted JS string literals, so a non-identifier value (API URL, Turnstile key) resolves in both. Environment variables. - Fix: web fonts and other CSS
url()assets no longer 404 inpyxle dev— Pyxle sets Vite'sserver.originso dev assets load from Vite directly.
0.4.4
- Fix: cross-page hash links scroll to their anchor. Client-side navigation to
/page#sectionnow jumps to the top and scrolls to the anchor once the next page commits, matching native behaviour.
0.4.3
- Security: hardened HEAD sanitisation (XSS). Dynamic
HEADvalues are now parsed and rebuilt from a strict tag allowlist with every attribute HTML-escaped,on*handlers dropped, dangerous URLs neutralised, and non-head tags rejected — closing an injection via the dynamic-meta-tags recipe. Inline<script>/<style>stays supported as trusted author code. - Security:
csrf.exemptPathsmatch on segment boundaries, so exempting/api/webhooksno longer also exempts/api/webhooks-admin. - Security: oversized no-JS form POSTs fail loud with a
413(asking for the token via header) instead of being silently truncated. - Fix:
<Script>/<Image>boolean attributes written as strings (defer="false",priority="0") now coerce correctly instead of toTrue. - Fix: custom
csrf.cookieName/headerNamenow reach the client runtime — non-default names are injected into the shell souseAction/<Form>stop403-ing. - Fix: no double loader run on hover-then-click navigation — the click reuses the in-flight prefetch, and a superseded prefetch is discarded.
- Multi-core serving:
pyxle serve --workers N. N independent server processes on one port, each with its own SSR pool — throughput scales with cores, no load balancer or shared state. Deployment. - Sync API endpoints. A plain
def endpoint(request)(and syncHTTPEndpointmethods) now runs in Starlette's threadpool, so blocking drivers no longer need manualasyncio.to_thread. API Routes. - In-memory static asset cache. Small static files (≤1 MB, 32 MB/process) are served from memory with no filesystem I/O; conditional requests and cache headers behave as before.
0.4.2
- Live dev-server reconciliation. Editing a
.pyxlnow applies route-shape changes without a restart — rename/add/remove a loader or action, add/delete a page, wrap a page in a layout — by hot-swapping the route table; editingpyxle.config.jsonprints a "restart to apply" warning. pyxle checkworks on a clean install — the JSX checker's parser dependencies are bundled (viapyxle-langkit), socheckruns afterpip install 'pyxle-framework[langkit]'with no npm setup.- Locale-independent SSR — the Python↔Node transport pins UTF-8, so non-BMP characters no longer crash rendering under
LANG=C. - Smoother first run —
pyxle initwrites a gitignored.env.localwith a random dev secret, the scaffoldrequirements.txtdeclarespyxle-framework, andpyxle installgives PEP 668 guidance. - Docs: documented calling an
@actionendpoint directly for scripts and tests.
0.4.1
- No more double loader run on first load. The landing page is seeded into the client navigation cache from the server render, so its prefetch resolves from cache — the loader runs once, not twice (also making back/forward instant).
- Per-route navigation-cache TTL. A route's
cacheTTL now also governs client navigation-cache freshness; routes without one default to 2 minutes, tunable vianavigation.defaultPrefetchTtl.
0.4.0
- Edge caching. Declare cacheable routes in
pyxle.config.json::cacheand pages serveCache-Control: public, s-maxage=N(+stale-while-revalidate) so a CDN absorbs traffic; the per-user CSRF cookie is omitted from cacheable responses. Deployment. - Hardened production errors. SSR render failures (including the SPA-navigation JSON path) are sanitized in the response and logged in full for operators.
- Faster static serving. The static-asset middleware indexes paths up front, skipping a per-request
staton every dynamic request. - Layout & template loaders. A
layout.pyxl/template.pyxlcan declare its own@serverloader, so shared UI loads once per request without repeating the loader in every page. Layouts.
0.3.0
- First-class plugin system. Compose apps via
pyxle.config.json::plugins(Django-styleINSTALLED_APPS). Plugins guide, Plugins API. - Django-style service access. Resolve any plugin service with
plugin("auth.service")or a typed shortcut (e.g.from pyxle_auth import get_auth_service). - First-party plugins.
pyxle-db(SQLite-first with migrations) andpyxle-auth(email+password sessions, argon2id, rate limits). - WebSocket endpoints —
pages/api/*.pycan exportasync def websocket(ws). API Routes. - Client navigation cache with TTL + invalidation. Loader payloads are cached (30s default) for instant back/forward; call
invalidate(url)or returninvalidate_routes(...)from an action to keep lists fresh. ActionErroris auto-imported for any.pyxlwith an@action.<Head>coerces multi-part<title>children into a single string, silencing React's array-title warning.- SSR worker pins
LANG=en-US.UTF-8(override withPYXLE_SSR_LOCALE) to stopIntlhydration mismatches. - Vite resolver prefers pinned versions —
pyxle buildrunsnpm installbefore falling back tonpx --yes vite.