Skip to content
Changelog
Edit
On this page

#Changelog

#0.12.2

Documentation frontmatter converted to TOML for the current selfdoc; no user-facing change.

#Infrastructure

  • Documentation frontmatter converted to TOML for the current selfdoc; no user-facing change.

#0.12.1

The project describes itself consistently on PyPI, npm and in its README, and its documentation base is the unified site.

#Fixes

  • The project describes itself consistently on PyPI, npm and in its README. The PyPI page had no long description or project links, the npm and Python descriptions differed, and the README opened with a tagline instead of a definition.
  • Documentation links point at the unified site. The declared docs base was the retired per-project host; it is https://smmh.dev/wesktop/ now, so generated sitemaps, feeds and llms.txt name the address that serves the pages.

#0.12.0

Native window control for frameless apps: WindowChrome, compositor-driven drag, input regions, window capture, headless runs, and windows that follow the desktop light/dark theme.

Context

The first consumer of these features is a frameless terminal emulator app built on wesktop. It needs the app to draw its own window frame, move and capture the window without a screen grab, follow the desktop theme, and run its server with no window under test; that is what this release adds.

#Breaking

  • **config set takes --value.** strictcli 0.41 turned the framework-owned config set into a selector: wesktop config set key --value v, --clear, or --default, replacing the positional value. The floor is now strictcli>=0.41.1; the config commands also carry fuller help text.

#Features

  • Window chrome. wesktop.run(chrome=WindowChrome(...)) opens frameless, transparent, always-on-top or custom-geometry native windows. Every pywebview window option is forwarded verbatim, with pywebview's own defaults, so an app can draw its own silhouette instead of a decorated rectangle.
  • Frameless windows that behave. wesktop.active_window() returns the live window handle, set_input_region() restricts pointer input to the region the page draws (so clicks on a transparent window's empty parts reach what is behind it), and begin_window_drag() asks the compositor for an interactive move -- the drag pywebview's easy_drag cannot do on Wayland.
  • Window capture. wesktop.capture_window(window, path) saves a PNG of the app's own window from the web view's own snapshot -- not a screen grab, so it needs no screen-capture permission, contains no other application's pixels, keeps the window's transparency, and works when the window is not on top. run(capture_to=...) takes the shot once the page has loaded.
  • Headless runs. with wesktop.headless("myapp:app") as app: starts the app's server with no window, hands back the URL, and stops it when the block ends -- including when it raises. pywebview is never imported, so a suite can drive a real wesktop app on a machine with no display, in CI, or over ssh.

#Fixes

  • Documentation links no longer dead-end. The SDUI reference link and the cross-links into the fastware documentation now point at pages that exist.
  • Pinch-to-zoom stays off. zoomable=False is now enforced rather than merely passed along: pywebview's GTK backend never read the flag, so a touchpad pinch or a ctrl+scroll rescaled the page of every GTK window. Those events are refused and the page is pinned at 1:1.
  • Windows follow the desktop theme. A GTK window now reports the desktop's light/dark preference to its page as prefers-color-scheme and keeps following it when the desktop changes -- previously it said "light" forever, so every page that follows the system rendered light on a dark desktop. wesktop.desktop_color_scheme() exposes the reading; run(follow_system_theme=False) opts out.

#0.11.1

Ships an sdist that contains only the package, its tests, its docs and its root metadata.

Context

The source distribution had no file selection, so hatchling's default shipped the entire repository. Every sdist from 0.8.1 through 0.11.0 therefore carried todo/, .rlsbl/, .claude/, .selfdoc/, CI workflows and lockfiles to PyPI -- including twenty screenshots of a private application's interface that were never meant to leave the machine. The screenshots are deleted, and the sdist now uses an allowlist rather than the implicit everything-minus-VCS-ignores default, so a newly added repository directory cannot ship by accident.

Wheels were never affected; they have always been built from src/wesktop.

#Fixes

  • sdists no longer include repository housekeeping files. The published source distribution now ships only src/, tests/, docs/, and the project's root metadata files. Earlier sdists carried the whole repository -- todo/, .rlsbl/, .claude/, .selfdoc/, CI workflows and lockfiles -- including image files that were never intended for publication.

#0.11.0

Restore the CLI on strictcli 0.36.0 and pin the floor that keeps it working, and expand the documentation site with a getting-started tutorial and a full SDUI primitive reference

Context

Two threads land together.

The first is the strictcli 0.36.0 migration. 0.36.0 made effect classification mandatory at registration and tightened handler-signature validation, which hard-errored wesktop's command registration -- wesktop --help, wesktop diagnose and any import of wesktop.cli all failed. diagnose is now classified read_only. The code fix alone was not enough: the dependency was declared unpinned, so a fresh install could still resolve a strictcli old enough to reinstate the breakage. The manifest now carries a strictcli>=0.36.0 floor, so an incompatible combination fails to resolve instead of installing and then failing to start.

The second is the documentation site. It gained a step-by-step getting-started tutorial and a reference page for every one of the 40 SDUI primitives, and the API reference was corrected against the shipped code -- surfaces it had drifted away from, or never documented, are now covered. Primitive counts quoted across the site are generated from the source rather than hand-maintained, so they cannot drift again.

This release also adopts the stricttest test-isolation floor. The stance is recorded in pyproject.toml and pinned by tests/test_stricttest_floor.py: off-machine egress is denied, both allowlists are empty, and loopback is allow rather than an exact allowlist because several suites bind real servers on ("127.0.0.1", 0) and the kernel-assigned port cannot be named ahead of time.

#Features

  • The documentation site gained a getting-started tutorial and a full SDUI primitive reference. A step-by-step tutorial walks from an empty directory to a packaged desktop app, and every one of the 40 SDUI primitives now has a reference page with its fields and an example. The API reference was also corrected against the shipped code -- surfaces it had drifted away from or never documented are now covered, and the primitive counts quoted across the site are generated from the source instead of hand-maintained.

#Fixes

  • The CLI works again on strictcli 0.36.0. strictcli made effect classification mandatory and tightened handler-signature validation, which hard-errored wesktop's command registration -- wesktop --help, wesktop diagnose and any import of wesktop.cli all failed. diagnose is now classified read_only.
  • **wesktop now requires strictcli>=0.36.0.** The dependency was unpinned, so a fresh install could resolve a strictcli old enough to reject wesktop's command registration -- reinstating the wesktop --help / wesktop diagnose breakage the same release fixes. Resolution now fails loudly instead of installing a combination that cannot start.

#0.10.0

Cross-process window lifecycle with per-window marker refcounting (fixes the shared-server kill bug), configurable second-launch behavior, a startup handshake with runtime config injection, and a native runtime bridge.

Context

This release builds on fastware 0.5.0's instance registry and presence-marker API to make multi-process desktop apps behave correctly.

Previously, when two launches shared one background server, closing the last window in one process stopped the server even though another process still had a window open. Window lifecycles are now refcounted across processes via per-window marker files in the fastware instance registry; the server stops only when the last live window (in any process) closes, with dead PIDs pruned by liveness.

wesktop.run(..., second_open=...) makes second-launch behavior an explicit choice: "new-window" (default) opens another window on the running server, or "focus-existing" drops a platform-neutral file-based focus request and exits so the running window is raised -- no DBus, no AppleEvents.

New windows run a startup handshake against /__fastware/version (a loud stderr error if it is unreachable or malformed, with the window staying open) and inject window.__wesktop = {buildId, port, appName} after page load (best-effort, retried once). list_app_instances() enumerates the app's live server and open windows. A host-side native runtime bridge reacts to fastware build changes.

#Features

  • Native desktop update bridge. New wesktop.runtime_bridge module lets a desktop host react to fastware build changes: reload(window), best-effort clear_web_cache(window), fetch_build_id(url), check_and_reload(...) (reloads on a DIFFERENT build id), and install_focus_poll(...) which polls the version endpoint on window focus where pywebview exposes one. run() now captures the native window handle and wires the bridge.
  • Desktop runtime wiring. wesktop.run(..., second_open=...) makes second-launch behavior an explicit choice: "new-window" (default) opens another window on the running server, or "focus-existing" drops a platform-neutral file-based focus request and exits so the running window is raised (no DBus/AppleEvents). New windows also run a startup handshake against /__fastware/version (loud stderr error if unreachable/malformed, window stays open), inject window.__wesktop = {buildId, port, appName} after page load (best-effort, retried once), and list_app_instances() enumerates the app's live server and open windows.

#Fixes

  • Shared-server kill bug fixed. When two launches share one background server, closing the last window in one process no longer stops the server while another process still has a window open. Window lifecycles are now refcounted across processes via per-window marker files; the server stops only when the last window (in any process) closes.

#0.9.0

Reference-counted window lifecycle with automatic server shutdown

#Features

  • New feature. Server stops automatically when the last native window closes, with reference counting for multi-window scenarios.

#0.8.1

Republish of the 0.8.x line: 0.8.0 never reached the registries due to a CI environment failure

Context

0.8.0 completed its git side (tag and GitHub Release) but the CI-gated publish refused because pytest flaked on GitHub runners: the search_files tests invoke ripgrep, which the CI workflow never installed and whose presence on runners is inconsistent. CI now installs ripgrep before pytest, so the search_files tests pass deterministically. 0.8.0 remains a git-tag-only release with no registry artifacts; 0.8.1 publishes the 0.8.x line to PyPI and npm.

#Fixes

  • 0.8.x now installable. The 0.8.0 release never reached PyPI/npm because its publish gate failed on a CI environment flake (missing ripgrep); 0.8.1 publishes the 0.8.x line.

#0.8.0

fastware 0.3.x compatibility and same-origin dev proxy support via backend_prefixes

Context

wesktop 0.7.0 only worked with fastware 0.1.0. This release tracks fastware's 0.3.x hardening and exposes the dev-proxy WebSocket routing via backend_prefixes so downstream apps can run same-origin dev mode. Also includes previously staged work: desktop entry fixes, MCP hardening, and SDUI fixes.

#Features

  • import wesktop is ~2-5x faster: server/desktop/dev/mcp/sdui symbols now load lazily (granian, mcp, and pydantic are no longer imported eagerly); WebSocketDisconnect is exported from the top level
  • The ask_user MCP tool's poll timeout is now configurable.
  • wesktop diagnose now reports the installed fastware version and whether it imports cleanly.
  • wesktop.dev() now accepts backend_prefixes, forwarded to fastware's ViteDevProxy so downstream apps can run same-origin dev mode: the browser loads everything from the Vite origin while /api and /ws proxy to the backend (no CORS). /ws WebSocket upgrades route to the backend and proxy WebSocket failures close with code 1011.
  • MCP role API is now documented. wesktop.ROLES, DEFAULT_ROLE, create_mcp_server, and register_tools_for_role appear in the API reference with role-registry and usage guidance.

#Fixes

  • Dependency installation no longer requires machine-local checkouts: uv.lock previously carried absolute local paths for strictcli and fastware, breaking installs and CI outside the author's machine. Dependencies now resolve from PyPI (strictcli 0.24.0, fastware 0.1.0).
  • wesktop now works with fastware 0.2.x: TestClient is imported explicitly, the agent role registry (ROLES/DEFAULT_ROLE) is owned by wesktop (pass roles=wesktop.ROLES to register_tools_for_role/create_mcp_server), and stale private server re-exports are gone
  • Desktop runtime hardening: run() detects the GUI backend truthfully per platform with a single probe, registers desktop entries with platform-aware and properly-quoted Exec commands, uses a stable PID path, and hard-errors on unsupported pre_serve/reload usage instead of silently misbehaving.
  • MCP filesystem and git tools harden path handling: the git_diff path-traversal guard is fixed and filesystem path handling is os.sep-safe (correct on Windows).
  • The deployment MCP tool now URL-quotes branch names, so branches containing slashes no longer break deployment requests.
  • SDUI serialization now honors pydantic field aliases, and Stack.to_node no longer emits duplicate child nodes.
  • wesktop now runs against fastware 0.3.1, inheriting its server, auth, WebSocket, routing, and static-file hardening plus a pinned asyncio event loop; serve() delegates with fastware's new pinned loop/workers defaults.
  • Desktop entry and launcher fixes: Windows shortcut commands parse with an explicit quoting contract (ambiguous unquoted paths hard-error instead of silently breaking), the COM vs PowerShell backend is selected up front by pywin32 availability with no silent fallback, .desktop Exec= escapes % per spec, and remove_entry also removes the -open launcher.
  • Fixed CLI command table in README. The generated README and docs site now show the actual diagnose and config command table instead of a "no strictcli app found" error.

#0.7.0

ASGI framework extracted to fastware. wesktop is now a thin desktop-app layer on top of fastware.

Context

All ASGI, SSE, middleware, server, auth, DI, testing, logging, tasks, features, error_log, audit, config, MCP, and dev modules have been extracted to the fastware package (v0.1.0). wesktop re-exports all symbols for backward compatibility, but the implementation lives in fastware. Four breaking changes: WebSocket binary frames, new server exception types, env var prefix rename, and fastware dependency added.

#Breaking

  • Breaking. Extracted ASGI framework modules to fastware package. wesktop now depends on fastware and re-exports all symbols. Import paths (from wesktop.asgi, from wesktop.server, etc.) continue to work but the underlying implementation is in fastware. Additional breaking changes: WebSocket send_json now sends binary frames (msgspec) instead of text frames; server errors raise PortInUseError/AlreadyRunningError instead of calling sys.exit(1); default env var prefix changed from WESKTOP_ to FASTWARE_.

#Features

  • Expanded CLI help texts for all commands and subcommands.

#0.6.0

Feature. serve_background() runs the server as an independent subprocess that survives the parent process exiting. run() now uses this for full window/server lifecycle separation.

#Features

  • Feature. serve_background() runs the server as an independent subprocess that survives the parent process exiting. run() now uses this for full window/server lifecycle separation.

#Fixes

  • Fix. Desktop entry launcher scripts no longer hardcode the working directory. Apps resolve paths from their package install location.

#0.5.0

Remove browser fallback from run(). run() no longer silently falls back to opening a browser when pywebview is unavailable. It now raises RuntimeError, making the failure explicit. Use serve() for headless/browser workflows.

#Breaking

  • Remove browser fallback from run(). run() no longer silently falls back to opening a browser when pywebview is unavailable. It now raises RuntimeError, making the failure explicit. Use serve() for headless/browser workflows.

#Features

  • Auto-register desktop entry. run() now automatically creates a desktop entry on first launch, removing the need for manual install commands.
  • PID resilience and single-instance mode. run() and serve() now handle stale PID files gracefully and support a single_instance parameter to prevent duplicate server instances.
  • Multi-window support. A second invocation of run() now opens a new native window connected to the existing server instead of restarting the server.

#Fixes

  • Resilient port check. Server startup now probes /health on the target port and can detect and kill stale server processes blocking the port, preventing silent startup failures.

#0.4.5

Feature. ensure_gui_backend() now searches macOS paths (Homebrew and Framework) for system PyGObject.

#Features

  • Feature. ensure_gui_backend() now searches macOS paths (Homebrew and Framework) for system PyGObject.

#Fixes

  • Fix. stop() now handles stale PID files gracefully instead of crashing with ProcessLookupError.

#0.4.4

Feature. ensure_gui_backend() finds and loads system PyGObject in isolated venvs. Called automatically by run().

#Features

  • Feature. ensure_gui_backend() finds and loads system PyGObject in isolated venvs. Called automatically by run().

#0.4.3

Fix. serve(foreground=False) signal handler patch now survives thread start race condition. Previous fix was ineffective because the noop was restored before the daemon thread executed startup.

#Fixes

  • Fix. serve(foreground=False) signal handler patch now survives thread start race condition. Previous fix was ineffective because the noop was restored before the daemon thread executed startup.

#0.4.2

Fix. run() no longer crashes when pywebview has no GUI backend available (e.g., missing GTK/Qt in uv virtual environments).

#Fixes

  • Fix. run() no longer crashes when pywebview has no GUI backend available (e.g., missing GTK/Qt in uv virtual environments).

#0.4.1

Fix. serve(foreground=False) no longer crashes with granian's signal handler registration in daemon threads. Fixes desktop mode (run()) and any background-server usage.

#Fixes

  • Fix. serve(foreground=False) no longer crashes with granian's signal handler registration in daemon threads. Fixes desktop mode (run()) and any background-server usage.
  • Fix. ViteDevProxy and dev() now use localhost instead of 127.0.0.1, fixing connectivity when Vite binds to IPv6 only.
  • Fix. ViteDevProxy now tries the backend first for all HTTP requests and only proxies to Vite on 404. Backend routes outside /api (like /health) are no longer incorrectly proxied.

#0.4.0

New feature. run() accepts a js_api parameter, passed through to pywebview's create_window() for exposing Python methods to JavaScript.

#Features

  • New feature. run() accepts a js_api parameter, passed through to pywebview's create_window() for exposing Python methods to JavaScript.
  • New feature. dev() starts a Vite dev server alongside the wesktop server in a single command, with ViteDevProxy for unified port access and automatic Vite lifecycle management.

#0.3.2

New feature. Forward reload parameter to run().

#Features

  • New feature. Forward reload parameter to run().

#Fixes

  • Fix. Query list coercion error handling, cached Request.state, typed Receive/Send aliases.
  • Fix. Merge lifespan state into WebSocket scope.
  • Fix. Replace private config path import with public config_file_path property.
  • Fix. Document optional MCP dependency in docstring.

#0.3.1

New feature. AppConfig dataclass for centralized app configuration via create_app().

#Features

  • New feature. AppConfig dataclass for centralized app configuration via create_app().
  • New feature. serve(reload=True) auto-restarts the server on .py file changes.
  • New feature. MCP module with tool registration and routing support.
  • New feature. WebSocket.receive_raw() for mixed binary+text frame handling.
  • New feature. Broadcaster heartbeat_interval for SSE keepalive.

#Fixes

  • Fix. Recursive Pydantic model serialization in WebSocket send results.
  • Fix. Corrected module docstring to describe wesktop as a full-featured ASGI framework.

#0.3.0

Breaking. serve() API redesigned: foreground parameter is required (no default), host/port have no implicit defaults (must be explicit or via env vars).

#Breaking

  • Breaking. serve() API redesigned: foreground parameter is required (no default), host/port have no implicit defaults (must be explicit or via env vars).
  • Breaking. Error response format changed from {"error": "..."} to {"detail": "..."}. Query parameter type coercion failure now raises 422 instead of silently returning default.
  • Breaking. Module-level add_ws_route() removed. Use Router.ws() or Router.add_ws_route() instead (WebSocket routes are now app-scoped).

#Features

  • Router. Typed path parameters ({id:int}), greedy {key:path} matching, PUT/PATCH decorators, router composition with include_router (prefix mounting + router-level dependencies), app-scoped WebSocket routing with path params, WebSocket helper class.
  • Request/Response. Body reading for all HTTP methods, query_list() for multi-value params, query validation with constraints, cookie extraction, response headers/cookies with set_cookie/delete_cookie helpers, FileResponse, StreamResponse with configurable status, request.method/path/is_disconnected properties, State wrapper.
  • Dependency injection. Per-request DI with sync/async factory support, generator cleanup, dependency caching, dependency overrides for testing, router-level dependencies, feature-gated dependencies.
  • Auth module. JWT create/verify (HS256), bcrypt password hashing, JSONFileUserStore, get_current_user/require_role DI dependencies, CSRF double-submit middleware, session cookie helpers, decorator-based rate limiting.
  • Middleware and observability. structlog integration with auto-detect (JSON/console), RequestID/RequestTiming middleware, CORS and TrustedHost middleware, built-in middleware wiring in create_app, optional Sentry integration, SQLite error log for 5xx responses.
  • Pydantic integration. response_model parameter on route decorators for response validation, req.json_as(Model) for request body parsing with 422 error detail.
  • Server lifecycle. Unified serve() with stop()/status() utilities, pre_serve callback, app-namespaced env var settings, Vite dev proxy middleware.
  • Dev experience. TestClient (sync + async), TOML config loader with optional Pydantic validation.
  • Advanced features. Feature flags with JSON overrides, JSONL audit logging, background task registry with feature gating, 39 SDUI UI primitives with provider registry.

#Fixes

  • Fix. Pydantic BaseModel auto-serialization in responses and api_prefix-aware SPA fallback.

#0.2.1

Fix. Use PyPI-published strictcli instead of local source override. Fixes CI test failures.

#Fixes

  • Fix. Use PyPI-published strictcli instead of local source override. Fixes CI test failures.

#0.2.0

Lazy JSON. Request.json is now lazily parsed with caching, improving performance for handlers that don't need the body.

#Features

  • Lazy JSON. Request.json is now lazily parsed with caching, improving performance for handlers that don't need the body.
  • CLI with diagnose command. New wesktop diagnose command checks Python, Node, pywebview, and granian availability. Config file support via strictcli.

#Fixes

  • Icon fix. The icon parameter is now correctly wired through to pywebview.

#0.1.1

No user-facing changes.

  • No user-facing changes.

#0.1.0

Initial release. ASGI router, SSE broadcaster, and extracted server lifecycle.

#Features

  • Initial release. ASGI router, SSE broadcaster, and extracted server lifecycle.
  • Desktop integration. pywebview, desktop entry creation, and public API exports.
  • npm shim. Install via npm i -g @smmh/wesktop for a global wesktop CLI that delegates to Python.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • selfdoc Static Site Generator that builds a project's documentation site directly from its source code, so the docs can never drift from the code they describe, with SEO/AEO, first-class blog, search, and cross-project linking built in
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
Search