pyweb-stack 0.5.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. pyweb_stack-0.6.0/CHANGELOG.md +1082 -0
  2. pyweb_stack-0.6.0/MANIFEST.in +3 -0
  3. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/PKG-INFO +117 -32
  4. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/README.md +105 -29
  5. pyweb_stack-0.6.0/THREAT_MODEL.md +201 -0
  6. pyweb_stack-0.6.0/docs/01-introduction.md +166 -0
  7. pyweb_stack-0.6.0/docs/02-quickstart.md +111 -0
  8. pyweb_stack-0.6.0/docs/03-tutorial.md +300 -0
  9. pyweb_stack-0.6.0/docs/04-pyweb-files.md +345 -0
  10. pyweb_stack-0.6.0/docs/05-reactivity.md +149 -0
  11. pyweb_stack-0.6.0/docs/06-server-functions.md +382 -0
  12. pyweb_stack-0.6.0/docs/07-browser-python.md +98 -0
  13. pyweb_stack-0.6.0/docs/08-pages-routing-assets.md +202 -0
  14. pyweb_stack-0.6.0/docs/09-data.md +432 -0
  15. pyweb_stack-0.6.0/docs/10-auth.md +324 -0
  16. pyweb_stack-0.6.0/docs/11-testing.md +193 -0
  17. pyweb_stack-0.6.0/docs/12-deployment.md +267 -0
  18. pyweb_stack-0.6.0/docs/13-security.md +282 -0
  19. pyweb_stack-0.6.0/docs/14-cli.md +150 -0
  20. pyweb_stack-0.6.0/docs/15-toolkit.md +110 -0
  21. pyweb_stack-0.6.0/docs/16-limitations-roadmap.md +70 -0
  22. pyweb_stack-0.6.0/docs/17-ai-assistants.md +223 -0
  23. pyweb_stack-0.6.0/docs/18-npm-packages.md +210 -0
  24. pyweb_stack-0.6.0/docs/19-layouts-navigation.md +148 -0
  25. pyweb_stack-0.6.0/docs/20-ai-apps.md +146 -0
  26. pyweb_stack-0.6.0/docs/21-live-data.md +130 -0
  27. pyweb_stack-0.6.0/docs/22-recipe-web3.md +146 -0
  28. pyweb_stack-0.6.0/docs/23-forms.md +192 -0
  29. pyweb_stack-0.6.0/docs/24-jobs.md +186 -0
  30. pyweb_stack-0.6.0/docs/25-scaling.md +89 -0
  31. pyweb_stack-0.6.0/docs/26-observability.md +207 -0
  32. pyweb_stack-0.6.0/docs/27-upgrading.md +205 -0
  33. pyweb_stack-0.6.0/docs/28-saas-tutorial.md +234 -0
  34. pyweb_stack-0.6.0/docs/29-performance.md +153 -0
  35. pyweb_stack-0.6.0/docs/30-teams.md +199 -0
  36. pyweb_stack-0.6.0/docs/31-pwa-push.md +168 -0
  37. pyweb_stack-0.6.0/docs/32-desktop.md +190 -0
  38. pyweb_stack-0.6.0/docs/33-webhooks-files.md +103 -0
  39. pyweb_stack-0.6.0/docs/34-ui-and-motion.md +537 -0
  40. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyproject.toml +12 -6
  41. pyweb_stack-0.6.0/pyweb/__init__.py +149 -0
  42. pyweb_stack-0.6.0/pyweb/_speedups.py +89 -0
  43. pyweb_stack-0.6.0/pyweb/_templates.py +3 -0
  44. pyweb_stack-0.6.0/pyweb/a11y.py +121 -0
  45. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/ai/guide.md +134 -11
  46. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/app.py +37 -6
  47. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/app_loader.py +111 -3
  48. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/asgi.py +6 -4
  49. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/auth.py +6 -0
  50. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/authkit/__init__.py +130 -4
  51. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/authkit/admin.py +25 -10
  52. pyweb_stack-0.6.0/pyweb/authkit/html.py +141 -0
  53. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/authkit/models.py +72 -1
  54. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/authkit/oauth.py +3 -0
  55. pyweb_stack-0.6.0/pyweb/authkit/orgpages.py +234 -0
  56. pyweb_stack-0.6.0/pyweb/authkit/orgs.py +98 -0
  57. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/authkit/pages.py +138 -11
  58. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/authkit/passwords.py +3 -0
  59. pyweb_stack-0.6.0/pyweb/authkit/privacy.py +101 -0
  60. pyweb_stack-0.6.0/pyweb/authkit/teams.py +286 -0
  61. pyweb_stack-0.6.0/pyweb/authkit/tokens.py +81 -0
  62. pyweb_stack-0.6.0/pyweb/bench.py +338 -0
  63. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/build.py +13 -0
  64. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/cli/__init__.py +199 -11
  65. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/compiler/lower.py +148 -22
  66. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/compiler/parser.py +40 -4
  67. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/compiler/pipeline.py +46 -9
  68. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/compiler/pyjs.py +67 -6
  69. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/config.py +31 -2
  70. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/context.py +29 -0
  71. pyweb_stack-0.6.0/pyweb/csp_report.py +116 -0
  72. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/db/__init__.py +39 -0
  73. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/db/migrate.py +2 -0
  74. pyweb_stack-0.6.0/pyweb/db/rls.py +120 -0
  75. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/db/schema.py +6 -0
  76. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/decorators.py +29 -3
  77. pyweb_stack-0.6.0/pyweb/desktop/__init__.py +243 -0
  78. pyweb_stack-0.6.0/pyweb/desktop/gate.py +164 -0
  79. pyweb_stack-0.6.0/pyweb/desktop/package.py +255 -0
  80. pyweb_stack-0.6.0/pyweb/desktop/shell.py +195 -0
  81. pyweb_stack-0.6.0/pyweb/devlink.py +236 -0
  82. pyweb_stack-0.6.0/pyweb/fieldcrypt.py +186 -0
  83. pyweb_stack-0.6.0/pyweb/filegc.py +116 -0
  84. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/forms.py +26 -5
  85. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/hosting.py +83 -8
  86. pyweb_stack-0.6.0/pyweb/http.py +265 -0
  87. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/jobs/core.py +2 -0
  88. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/jobs/memory.py +2 -0
  89. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/jobs/worker.py +5 -1
  90. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/livedata.py +4 -3
  91. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/lsp.py +13 -0
  92. pyweb_stack-0.6.0/pyweb/lsp_teams.py +139 -0
  93. pyweb_stack-0.6.0/pyweb/lsp_ui.py +158 -0
  94. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/mail.py +3 -0
  95. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/mcp.py +696 -132
  96. pyweb_stack-0.6.0/pyweb/mcp_http.py +219 -0
  97. pyweb_stack-0.6.0/pyweb/mcp_tools.py +686 -0
  98. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/models/__init__.py +104 -11
  99. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/models/fields.py +50 -1
  100. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/models/query.py +43 -0
  101. pyweb_stack-0.6.0/pyweb/motion.py +282 -0
  102. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/net/live.py +2 -1
  103. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/net/server.py +6 -3
  104. pyweb_stack-0.6.0/pyweb/openapi.py +228 -0
  105. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/packages.py +3 -0
  106. pyweb_stack-0.6.0/pyweb/platform.py +100 -0
  107. pyweb_stack-0.6.0/pyweb/profile.py +123 -0
  108. pyweb_stack-0.6.0/pyweb/push.py +468 -0
  109. pyweb_stack-0.6.0/pyweb/pwa.py +423 -0
  110. pyweb_stack-0.6.0/pyweb/runtime/browser/desktop.js +41 -0
  111. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/runtime/browser/markdown.js +8 -1
  112. pyweb_stack-0.6.0/pyweb/runtime/browser/motion-api.js +68 -0
  113. pyweb_stack-0.6.0/pyweb/runtime/browser/motion-count.js +48 -0
  114. pyweb_stack-0.6.0/pyweb/runtime/browser/motion-drag.js +105 -0
  115. pyweb_stack-0.6.0/pyweb/runtime/browser/motion-layout.js +71 -0
  116. pyweb_stack-0.6.0/pyweb/runtime/browser/motion-math.js +132 -0
  117. pyweb_stack-0.6.0/pyweb/runtime/browser/motion.js +498 -0
  118. pyweb_stack-0.6.0/pyweb/runtime/browser/pwa.js +120 -0
  119. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/runtime/browser/runtime.js +118 -20
  120. pyweb_stack-0.6.0/pyweb/runtime/browser/sw.js +149 -0
  121. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/accordion.js +89 -0
  122. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/calendar.js +162 -0
  123. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/carousel.js +105 -0
  124. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/checkbox.js +9 -0
  125. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/collapsible.js +12 -0
  126. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/combobox.js +91 -0
  127. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/command.js +72 -0
  128. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/context-menu.js +36 -0
  129. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/core.js +286 -0
  130. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/dialog.js +89 -0
  131. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/hover-card.js +39 -0
  132. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/index.js +86 -0
  133. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/listbox.js +74 -0
  134. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/menu.js +168 -0
  135. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/menubar.js +58 -0
  136. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/navigation-menu.js +94 -0
  137. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/otp.js +29 -0
  138. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/popover.js +75 -0
  139. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/radio-group.js +20 -0
  140. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/resizable.js +101 -0
  141. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/select.js +104 -0
  142. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/sidebar.js +71 -0
  143. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/slider.js +19 -0
  144. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/tabs.js +74 -0
  145. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/toaster.js +158 -0
  146. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/toggle-group.js +45 -0
  147. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/toggle.js +15 -0
  148. pyweb_stack-0.6.0/pyweb/runtime/browser/ui/tooltip.js +61 -0
  149. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/runtime/server/__init__.py +125 -15
  150. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/runtime/server/argcheck.py +30 -0
  151. pyweb_stack-0.6.0/pyweb/sbom.py +147 -0
  152. pyweb_stack-0.6.0/pyweb/secret_scan.py +102 -0
  153. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/security.py +3 -1
  154. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/serve.py +18 -1
  155. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/ssr.py +173 -6
  156. pyweb_stack-0.6.0/pyweb/ssr_codegen.py +263 -0
  157. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/storage.py +13 -1
  158. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/telemetry/devtools.py +168 -1
  159. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/telemetry/instruments.py +2 -0
  160. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/telemetry/logs.py +31 -9
  161. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/telemetry/metrics.py +5 -0
  162. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/telemetry/tracing.py +16 -6
  163. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/templates/ai-chat.pyweb +9 -7
  164. pyweb_stack-0.6.0/pyweb/templates/app.css +105 -0
  165. pyweb_stack-0.6.0/pyweb/templates/auth.pyweb +135 -0
  166. pyweb_stack-0.6.0/pyweb/templates/blog.pyweb +87 -0
  167. pyweb_stack-0.6.0/pyweb/templates/chat.pyweb +100 -0
  168. pyweb_stack-0.6.0/pyweb/templates/counter.pyweb +37 -0
  169. pyweb_stack-0.6.0/pyweb/templates/desktop/app.pyweb +125 -0
  170. pyweb_stack-0.6.0/pyweb/templates/desktop/test_app.py +58 -0
  171. pyweb_stack-0.6.0/pyweb/templates/saas/app.pyweb +269 -0
  172. pyweb_stack-0.6.0/pyweb/templates/saas/seeds.py +19 -0
  173. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/templates/saas/test_app.py +28 -4
  174. pyweb_stack-0.6.0/pyweb/templates/todo.pyweb +84 -0
  175. pyweb_stack-0.6.0/pyweb/tenancy.py +123 -0
  176. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/testing.py +59 -6
  177. pyweb_stack-0.6.0/pyweb/ui/__init__.py +96 -0
  178. pyweb_stack-0.6.0/pyweb/ui/cli.py +86 -0
  179. pyweb_stack-0.6.0/pyweb/ui/extending.py +140 -0
  180. pyweb_stack-0.6.0/pyweb/ui/kit/button.pyweb +25 -0
  181. pyweb_stack-0.6.0/pyweb/ui/kit/card.pyweb +25 -0
  182. pyweb_stack-0.6.0/pyweb/ui/kit/carousel.pyweb +41 -0
  183. pyweb_stack-0.6.0/pyweb/ui/kit/chart.pyweb +292 -0
  184. pyweb_stack-0.6.0/pyweb/ui/kit/command.pyweb +113 -0
  185. pyweb_stack-0.6.0/pyweb/ui/kit/controls.pyweb +131 -0
  186. pyweb_stack-0.6.0/pyweb/ui/kit/data.pyweb +202 -0
  187. pyweb_stack-0.6.0/pyweb/ui/kit/date.pyweb +35 -0
  188. pyweb_stack-0.6.0/pyweb/ui/kit/dialog.pyweb +137 -0
  189. pyweb_stack-0.6.0/pyweb/ui/kit/disclosure.pyweb +85 -0
  190. pyweb_stack-0.6.0/pyweb/ui/kit/display.pyweb +59 -0
  191. pyweb_stack-0.6.0/pyweb/ui/kit/layout.pyweb +264 -0
  192. pyweb_stack-0.6.0/pyweb/ui/kit/menu.pyweb +130 -0
  193. pyweb_stack-0.6.0/pyweb/ui/kit/overlay.pyweb +65 -0
  194. pyweb_stack-0.6.0/pyweb/ui/kit/style.pyweb +118 -0
  195. pyweb_stack-0.6.0/pyweb/ui/serve.py +96 -0
  196. pyweb_stack-0.6.0/pyweb/ui/source_edit.py +111 -0
  197. pyweb_stack-0.6.0/pyweb/ui/static/ui.css +617 -0
  198. pyweb_stack-0.6.0/pyweb/ui/theme.py +293 -0
  199. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/upgrade.py +59 -0
  200. pyweb_stack-0.6.0/pyweb/webhooks.py +334 -0
  201. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb_stack.egg-info/PKG-INFO +117 -32
  202. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb_stack.egg-info/SOURCES.txt +160 -0
  203. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb_stack.egg-info/requires.txt +10 -0
  204. pyweb_stack-0.6.0/setup.py +24 -0
  205. pyweb_stack-0.6.0/tests/test_account_privacy.py +79 -0
  206. pyweb_stack-0.6.0/tests/test_api_tokens.py +107 -0
  207. pyweb_stack-0.6.0/tests/test_auth_sessions.py +99 -0
  208. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_authkit.py +28 -1
  209. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_budget.py +1 -1
  210. pyweb_stack-0.6.0/tests/test_customise.py +286 -0
  211. pyweb_stack-0.6.0/tests/test_desktop.py +467 -0
  212. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_docs.py +4 -2
  213. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_dx_v5.py +3 -1
  214. pyweb_stack-0.6.0/tests/test_encrypted_fields.py +151 -0
  215. pyweb_stack-0.6.0/tests/test_filegc.py +65 -0
  216. pyweb_stack-0.6.0/tests/test_fuzz_requests.py +143 -0
  217. pyweb_stack-0.6.0/tests/test_http_fetch.py +136 -0
  218. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_jobs_app.py +5 -1
  219. pyweb_stack-0.6.0/tests/test_lsp_teams.py +67 -0
  220. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_mcp.py +22 -6
  221. pyweb_stack-0.6.0/tests/test_mcp_teams.py +54 -0
  222. pyweb_stack-0.6.0/tests/test_mcp_v6.py +401 -0
  223. pyweb_stack-0.6.0/tests/test_motion.py +268 -0
  224. pyweb_stack-0.6.0/tests/test_openapi.py +107 -0
  225. pyweb_stack-0.6.0/tests/test_org_pages.py +150 -0
  226. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_plugins_platform_bench.py +21 -0
  227. pyweb_stack-0.6.0/tests/test_profile.py +63 -0
  228. pyweb_stack-0.6.0/tests/test_push.py +284 -0
  229. pyweb_stack-0.6.0/tests/test_pwa.py +199 -0
  230. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_pyjs_semantics.py +20 -2
  231. pyweb_stack-0.6.0/tests/test_rate_per_function.py +80 -0
  232. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_reference_apps.py +2 -1
  233. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_request_hardening.py +26 -0
  234. pyweb_stack-0.6.0/tests/test_sbom.py +62 -0
  235. pyweb_stack-0.6.0/tests/test_secret_scan.py +52 -0
  236. pyweb_stack-0.6.0/tests/test_speedups.py +57 -0
  237. pyweb_stack-0.6.0/tests/test_ssr_codegen.py +123 -0
  238. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_systems.py +80 -0
  239. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_telemetry.py +40 -0
  240. pyweb_stack-0.6.0/tests/test_tenancy.py +321 -0
  241. pyweb_stack-0.6.0/tests/test_testing_orgs.py +116 -0
  242. pyweb_stack-0.6.0/tests/test_threat_model.py +20 -0
  243. pyweb_stack-0.6.0/tests/test_trusted_types.py +95 -0
  244. pyweb_stack-0.6.0/tests/test_ui_kit.py +469 -0
  245. pyweb_stack-0.6.0/tests/test_webhooks.py +161 -0
  246. pyweb_stack-0.5.0/pyweb/__init__.py +0 -102
  247. pyweb_stack-0.5.0/pyweb/authkit/html.py +0 -107
  248. pyweb_stack-0.5.0/pyweb/bench.py +0 -125
  249. pyweb_stack-0.5.0/pyweb/platform.py +0 -69
  250. pyweb_stack-0.5.0/pyweb/templates/app.css +0 -50
  251. pyweb_stack-0.5.0/pyweb/templates/auth.pyweb +0 -111
  252. pyweb_stack-0.5.0/pyweb/templates/blog.pyweb +0 -75
  253. pyweb_stack-0.5.0/pyweb/templates/chat.pyweb +0 -81
  254. pyweb_stack-0.5.0/pyweb/templates/counter.pyweb +0 -26
  255. pyweb_stack-0.5.0/pyweb/templates/saas/app.pyweb +0 -168
  256. pyweb_stack-0.5.0/pyweb/templates/saas/seeds.py +0 -14
  257. pyweb_stack-0.5.0/pyweb/templates/todo.pyweb +0 -72
  258. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/LICENSE +0 -0
  259. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/authkit/throttle.py +0 -0
  260. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/authkit/webauthn.py +0 -0
  261. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/browser.py +0 -0
  262. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/cache.py +0 -0
  263. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/cli/__main__.py +0 -0
  264. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/compiler/__init__.py +0 -0
  265. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/compiler/ast.py +0 -0
  266. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/compiler/codegen/__init__.py +0 -0
  267. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/compiler/codegen/ir.py +0 -0
  268. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/compiler/errors.py +0 -0
  269. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/compiler/rpc.py +0 -0
  270. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/css.py +0 -0
  271. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/db/dialect.py +0 -0
  272. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/db/seeds.py +0 -0
  273. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/deploy/__init__.py +0 -0
  274. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/deploy/legacy.py +0 -0
  275. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/deploy/plan.py +0 -0
  276. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/deploy/targets.py +0 -0
  277. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/deprecation.py +0 -0
  278. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/dts.py +0 -0
  279. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/jobs/__init__.py +0 -0
  280. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/jobs/builtin.py +0 -0
  281. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/jobs/db.py +0 -0
  282. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/jobs/legacy.py +0 -0
  283. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/jobs/redis.py +0 -0
  284. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/jobs/schedule.py +0 -0
  285. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/keys.py +0 -0
  286. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/livetable.py +0 -0
  287. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/lsp_data.py +0 -0
  288. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/markdown.py +0 -0
  289. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/models/relations.py +0 -0
  290. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/net/__init__.py +0 -0
  291. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/net/http.py +0 -0
  292. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/net/ws.py +0 -0
  293. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/observability.py +0 -0
  294. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/plugins.py +0 -0
  295. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/py.typed +0 -0
  296. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/realtime.py +0 -0
  297. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/rooms.py +0 -0
  298. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/rpc.py +0 -0
  299. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/rules.py +0 -0
  300. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/runtime/browser/auth.js +0 -0
  301. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/runtime/browser/forms.js +0 -0
  302. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/runtime/browser/live.js +0 -0
  303. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/sync.py +0 -0
  304. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/telemetry/__init__.py +0 -0
  305. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb/uploads.py +0 -0
  306. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb_stack.egg-info/dependency_links.txt +0 -0
  307. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb_stack.egg-info/entry_points.txt +0 -0
  308. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/pyweb_stack.egg-info/top_level.txt +0 -0
  309. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/setup.cfg +0 -0
  310. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_auth.py +0 -0
  311. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_auth_contract.py +0 -0
  312. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_auth_security.py +0 -0
  313. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_auth_v1.py +0 -0
  314. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_authkit_units.py +0 -0
  315. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_backplane.py +0 -0
  316. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_browser_build.py +0 -0
  317. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_cli_deploy.py +0 -0
  318. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_codegen.py +0 -0
  319. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_compiler.py +0 -0
  320. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_cron.py +0 -0
  321. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_data_layer.py +0 -0
  322. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_db_production.py +0 -0
  323. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_deploy_upload_render.py +0 -0
  324. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_deploy_v5.py +0 -0
  325. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_dts.py +0 -0
  326. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_e2e.py +0 -0
  327. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_examples.py +0 -0
  328. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_forms_security_obs.py +0 -0
  329. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_forms_v5.py +0 -0
  330. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_jobs_cache_realtime.py +0 -0
  331. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_jobs_v5.py +0 -0
  332. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_keys_sessions.py +0 -0
  333. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_live_patches.py +0 -0
  334. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_livedata.py +0 -0
  335. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_lsp.py +0 -0
  336. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_markdown.py +0 -0
  337. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_migrations_property.py +0 -0
  338. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_migrations_v5.py +0 -0
  339. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_models_db.py +0 -0
  340. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_multifile.py +0 -0
  341. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_multipage.py +0 -0
  342. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_net.py +0 -0
  343. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_observability.py +0 -0
  344. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_packages.py +0 -0
  345. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_parser.py +0 -0
  346. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_parser_v1.py +0 -0
  347. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_production_gaps.py +0 -0
  348. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_reactivity.py +0 -0
  349. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_realtime_transport.py +0 -0
  350. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_rpc_placement.py +0 -0
  351. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_rpc_production.py +0 -0
  352. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_runtime_signals.py +0 -0
  353. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_server_db.py +0 -0
  354. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_static_hashing.py +0 -0
  355. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_static_serving.py +0 -0
  356. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_streaming.py +0 -0
  357. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_sync_live.py +0 -0
  358. {pyweb_stack-0.5.0 → pyweb_stack-0.6.0}/tests/test_website.py +0 -0
@@ -0,0 +1,1082 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format
4
+ follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
5
+ project uses [semantic versioning](https://semver.org).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.6.0]
10
+
11
+ Teams, installable and desktop apps, a UI kit and motion, and security by
12
+ design: organizations with roles, invitations and tenant-scoped Models
13
+ (enforced by the database on Postgres), API tokens and OpenAPI, webhooks,
14
+ PWA and Web Push, native desktop windows and installers, a styled and
15
+ accessible component kit with springs and layout animations, Trusted
16
+ Types, encrypted fields and a threat model, about 2.5× faster serving,
17
+ Python 3.15 and free-threaded builds, and a much more capable MCP server.
18
+ Most 0.5 apps run unchanged; see [Upgrading to 0.6](docs/27-upgrading.md)
19
+ and run `pyweb upgrade --check`.
20
+
21
+ ### Added
22
+ - **Python 3.14, 3.15 and free-threaded 3.15t** are supported and tested in
23
+ CI. On a free-threaded build one `pyweb serve` process uses every core
24
+ (about 3× the requests per second of the same build with the GIL).
25
+ - **Python 3.15 features.** Browser code can use unpacking in
26
+ comprehensions (`[*row for row in rows]`, `{**d for d in ds}`). `lazy
27
+ import` is server-only Python, and older Pythons get a clear error that
28
+ names the version it needs.
29
+ - **`pyweb profile app.pyweb`** serves the app and records where the time
30
+ goes while you use it: a Tachyon flame graph on Python 3.15, a text
31
+ report before.
32
+ - **`pyweb-stack[speed]`** adds orjson for live queries, WebSocket messages,
33
+ page state and server function results (a 10,000-row live query encodes
34
+ 10× faster).
35
+ - **`python -m pyweb.bench --system`** measures requests per second over
36
+ sockets, a signed-in page, start-up and live queries; `--compare DIR`
37
+ fails on a 15% regression against another checkout. CI runs it on
38
+ every push.
39
+ - **`PYWEB_SSR_VERIFY=1`** renders every page with both renderers and
40
+ fails if they differ.
41
+ - New docs page: [Performance](docs/29-performance.md), including why
42
+ PyWeb has no Rust core.
43
+ - **Trusted Types.** Pages tell the browser to refuse plain strings in
44
+ HTML and script sinks; PyWeb's runtime uses private policies
45
+ (Markdown, client navigation, the service worker). Pages that use npm packages are exempt.
46
+ - **Encrypted fields.** `Field(encrypted=True)` stores AES-256-GCM
47
+ ciphertext under `PYWEB_DATA_KEY` (kept apart from the session secret);
48
+ `searchable=True` adds a blind index for exact lookups. Rotate with
49
+ `PYWEB_DATA_KEY_PREVIOUS` and `pyweb db reencrypt`.
50
+ - **Secret scanning.** `pyweb check` finds keys written into the app and
51
+ the files next to it: known formats fail the check, random-looking values
52
+ in secret-named variables are warnings.
53
+ - **`pyweb.http.fetch`** for URLs other people choose: refuses internal
54
+ addresses (including through redirects and DNS rebinding), with time and
55
+ size limits. `PYWEB_FETCH_ALLOW` lets it reach internal services you name.
56
+ - **`@server(rate="5/minute", per="user"|"ip"|"all")`** limits one server
57
+ function (429 with `Retry-After`; shared through Redis when configured).
58
+ - **Webhooks.** `@app.webhook("/hooks/stripe", secret=..., scheme="stripe")`
59
+ checks GitHub, Stripe and Standard Webhooks signatures, drops repeats
60
+ and replays, and runs the handler as a retried job. `webhooks.send()` /
61
+ `publish()` sign deliveries (Standard Webhooks), retry them over about a
62
+ day through the SSRF-safe fetch, and log each attempt; `WebhookEndpoint`
63
+ rows hold each organization's endpoints.
64
+ - **Installable apps.** `App(pwa=True)` (or `pwa=PWA(name=, icon=,
65
+ theme_color=, offline_page=, cache_pages=...)`) serves a web app
66
+ manifest, icons made from one PNG (or drawn for you) and a service
67
+ worker: static files work offline, pages without a connection show an
68
+ offline page, and with `cache_pages=True` pages visitors opened while
69
+ signed out stay readable offline. The worker is registered through a
70
+ third Trusted Types policy, `pyweb-sw`, which accepts only `/sw.js`.
71
+ - **A UI kit** (`from pyweb.ui import Button, Card, Badge, Alert, ...`):
72
+ themed, accessible, server-rendered components. `App(theme=Theme(accent=,
73
+ radius=, font=, density=, dark=))` or a preset computes 12-step OKLCH
74
+ colour scales (accent, gray, status colours; light and dark) with
75
+ contrast checked to 4.5:1. The kit's CSS sits in a cascade layer so
76
+ app CSS always wins, and every part carries `data-part`/`data-state`
77
+ attributes. `pyweb ui add|diff|list` copies a component's source into
78
+ the app. Overlays and pickers follow the WAI-ARIA Authoring Practices:
79
+ Dialog, AlertDialog, Sheet, Popover, Tooltip, HoverCard, DropdownMenu,
80
+ ContextMenu, Menubar (with checkbox/radio items and submenus),
81
+ SelectMenu, Combobox, a Command palette (⌘K) and sonner-style toasts
82
+ (`pyweb.ui.toast()` from server code). Also Tabs, Accordion and
83
+ Collapsible (native `<details>`), Checkbox, Switch, RadioGroup, Toggle,
84
+ ToggleGroup, Slider, InputOTP, a Sidebar layout, NavigationMenu,
85
+ Breadcrumb, Pagination, Resizable panels, ScrollArea, Calendar and
86
+ DatePicker, Carousel, DataTable (search, sort, pages, row selection) and
87
+ SVG charts (bar, line, area, pie, sparkline) drawn on the server. Each
88
+ behaviour module loads only on pages that use it; `from pyweb.ui import *`
89
+ imports them all.
90
+ - **Motion** (in the spirit of motion.dev): `initial=`, `animate=`,
91
+ `exit=` and `transition=` on any element, springs that keep their
92
+ momentum when interrupted, presets (`animate="fade-up"`), variants with
93
+ staggered children, `while_hover`/`while_tap`/`while_focus`/
94
+ `while_in_view`, exit animations for `if` and `for`, `layout` (rows glide
95
+ to their new places, keeping focus and input values), `layout_id`
96
+ (shared elements), `drag` with constraints and momentum, counting
97
+ numbers (`animate="count"`), View Transitions on client navigation, and
98
+ `from pyweb.motion import animate, timeline, stagger, scroll_progress,
99
+ use_transform` for browser code. `App(motion=Motion(feel=, speed=,
100
+ reduced=))` changes every animation at once, the UI kit's included, and
101
+ reduced-motion settings are followed. The server renders the first
102
+ pose; pages without motion don't load the engine (7.5 KB gzipped).
103
+ - **Customising the kit, every level.**
104
+ - The dev toolbar gets a **Theme** tab: a live preview on the running
105
+ app, copy as code, and "Save to app.pyweb", which rewrites only the
106
+ `App(theme=...)` argument.
107
+ - It also gets a **Motion** tab: pick an animated element, tune its
108
+ spring or duration, and replay it.
109
+ - `ui.extend("Button", variant={"brand": {...}})` adds your own
110
+ variants to kit components.
111
+ - `variants()`, `style()` and `cx()` (cva and tailwind-merge style)
112
+ are for your own components, on the server and in the browser.
113
+ - The editor completes variant, size and tone values, presets,
114
+ `Theme(...)`/`Motion(...)` settings and transition keys, and warns
115
+ about unknown ones or out-of-range springs.
116
+ - **A "UI kit and motion" example** (`examples/ui`): dialogs, menus,
117
+ pickers, toasts, a data table and charts, plus staggered, gesture,
118
+ drag, layout and shared-element animations. It runs on the website, as
119
+ a live demo and in the playground.
120
+ - `MenuItem(href=...)` makes a menu item a link.
121
+ - **The UI kit is tested in Chromium, Firefox and WebKit** (keyboard
122
+ behaviour of every component, motion and the examples), with
123
+ screenshot baselines per component in light and dark, and axe-core
124
+ finding no serious violations on the examples, the auth pages and
125
+ `/admin`.
126
+ - **Accessibility checks** in `pyweb check` and the editor: images
127
+ without alt, clicks keyboards can't reach, unlabelled fields, unnamed
128
+ buttons and links, positive tabindex, an empty page language. The
129
+ examples and templates were fixed where they fell short.
130
+ - **Testing apps with organizations:** `client.login(email, org="Acme",
131
+ org_role="admin")` signs in as a member of that organization (created
132
+ if needed) and makes it the current one; `with as_org(org):` runs code
133
+ for an organization; `Factory(...).for_org(org)` makes tenant rows in it.
134
+ - **`@app.page(..., org_role=..., feature=...)`** (and on layouts), like
135
+ `@server`: the page needs that role in the current organization, or a
136
+ plan with that feature (403 otherwise).
137
+ - **Editor:** completions for `@server(...)` and `@app.page(...)` options,
138
+ `org_role=`, `per=`, `rate=`, plan features, `tenant=True` and webhook
139
+ schemes; warnings for a malformed `rate=`, an unknown role, and a
140
+ `tenant=True` Model read with `.unscoped()` outside `with system():`.
141
+ - **A much better MCP server** (29 tools, up from 18):
142
+ - new tools: `pyweb_docs` (search every docs page, the guide and the
143
+ threat model), `pyweb_ui` (the kit's components, their parameters and
144
+ source, copying one into your app, making and saving a theme),
145
+ `pyweb_review` (screenshots in light and dark at desktop and phone
146
+ widths with axe-core's audit), `pyweb_explain` (what your editor shows
147
+ for a line: where each name runs, problems, completions),
148
+ `pyweb_profile`, `pyweb_webhook_send` (a correctly signed test
149
+ delivery, then the handler's result), `pyweb_teams`, `pyweb_upgrade`,
150
+ `pyweb_deploy_plan`, `pyweb_openapi` and `pyweb_security_check`;
151
+ - `pyweb_dev` and `pyweb_logs` see your running `pyweb dev`: whether the
152
+ app compiles (with the traceback), its logs and the requests your
153
+ browser made. `pyweb dev` writes `.pyweb/dev.json` (its address and a
154
+ token readable only by you); `pyweb_screenshot` and `pyweb_review` use
155
+ it too;
156
+ - `pyweb_render`, `pyweb_call`, `pyweb_screenshot` and `pyweb_profile`
157
+ act as a signed-in user and organization member (`as_user`, `roles`,
158
+ `org`, `org_role`);
159
+ - MCP 2025-11-25: every tool has a title, read-only/destructive hints
160
+ and an output schema; arguments are checked before a tool runs; slow
161
+ tools report progress; logging; completion of resource and prompt
162
+ arguments; resources for every docs page (`pyweb://docs/<page>`), kit
163
+ component (`pyweb://kit/<Component>`) and the threat model;
164
+ - eight new prompts: `add_teams`, `make_installable`, `make_desktop_app`,
165
+ `security_review`, `upgrade_pyweb`, `design_pass`, `fix_failing_test`
166
+ and `debug_page`;
167
+ - `pyweb mcp --http` serves Streamable HTTP on 127.0.0.1 (sessions,
168
+ event streams for progress; `Origin` and `Host` checked; a token is
169
+ required to listen anywhere else).
170
+ - The documentation ships inside the package, for the MCP server.
171
+ - **`pyweb upgrade`** points at stylesheets written for the 0.5 sign-in and
172
+ admin pages' classes.
173
+ - **Playground:** apps with teams get a "Team member" visitor next to
174
+ "Signed in" (both in one demo team, so you can see what each role sees),
175
+ and installable apps get an Install tab with their manifest and icons.
176
+ - **Docs:** [Upgrading to 0.6](docs/27-upgrading.md), testing apps with
177
+ teams, and the SaaS tutorial rewritten around teams.
178
+ - **New `desktop` template** (`pyweb new notes --template desktop`): a
179
+ Markdown notes app in its own window that exports a note with the
180
+ system's Save dialog, with its tests and packaging instructions.
181
+ - **Plain functions from other `.pyweb` files work in browser code**,
182
+ along with the functions and constants they use.
183
+ - **`math` in browser code.** `import math` (and `from math import pi`)
184
+ works in browser code, translated to JavaScript's `Math`.
185
+ - **Components pass attributes through.** A `**attrs` parameter takes any
186
+ other attribute, and `{**attrs}` spreads a dict onto an element
187
+ (explicit attributes win, classes merge).
188
+ - **Desktop apps.** `pyweb desktop app.pyweb` opens the app in its own
189
+ window (pywebview: WebView2, WKWebView, WebKitGTK or Qt), with its
190
+ database, uploads and per-installation secrets in the OS's data folder.
191
+ Only the window gets in: a one-time launch cookie, an exact `Host` and
192
+ same-origin requests are required, and links to other sites open in the
193
+ browser. `pyweb.desktop` gives server code file dialogs, notifications,
194
+ "reveal in folder" and the data folder; `App(desktop=Window(...))` sets
195
+ the window. `pyweb build --desktop [--package]` writes a Briefcase
196
+ project with icons for every OS and builds the installer (MSI, DMG,
197
+ `.deb`/`.rpm`). `pyweb-stack[desktop]` installs what it needs.
198
+ - **Push notifications.** A `data-pw-push="subscribe"` button asks for
199
+ permission; `push.send(user, "Title", "Body", url="/orders/42")` encrypts
200
+ (RFC 8291), signs (VAPID) and delivers through background jobs, deleting
201
+ subscriptions the push service says are gone. Only the browsers' push
202
+ services are accepted as endpoints. `pyweb push key` makes a
203
+ `PYWEB_VAPID_KEY`; `pyweb-stack[push]` installs what it needs.
204
+ - **Uploaded files are cleaned up.** Files no row refers to are deleted
205
+ after a day (daily, or `pyweb files clean`).
206
+ - **API tokens and OpenAPI.** `/account/tokens` makes tokens for calling
207
+ server functions with `Authorization: Bearer` (scoped to functions,
208
+ optionally expiring, stored as hashes); `pyweb openapi app.pyweb` writes
209
+ an OpenAPI 3.1 document from the functions' types, rules and guards.
210
+ - **Your data.** `/account` offers a JSON download of everything stored
211
+ about the user (`auth.export_user()`) and deleting the account
212
+ (`auth.delete_user()`, after a recent sign-in and typing the email).
213
+ - **Signed-in devices.** `/account` lists each session (device, network,
214
+ last seen) and can sign one out; `auth.sessions(user)` and
215
+ `auth.end_session(user, id)` do the same from code. Apps with migrations
216
+ need one for the new `auth_sessions` table (`pyweb upgrade` says so);
217
+ until then sign-in works as before.
218
+ - **Supply chain.** `pyweb build` writes a CycloneDX `sbom.json`; releases
219
+ get PEP 740 attestations on PyPI and a CycloneDX file; CI pins every
220
+ GitHub Action to a commit.
221
+ - **Teams.** `app.use_auth(orgs=True)` adds organizations, members with
222
+ roles (owner, admin, member, viewer), emailed invitations and the pages
223
+ to manage them (`/orgs`). `class Project(Model, tenant=True)` keeps each
224
+ organization's rows to itself: queries, saves, bulk updates, live lists
225
+ and background jobs are all limited to the current organization.
226
+ `@server(org_role="admin", feature="exports")` and `rate=..., per="org"`;
227
+ `auth.plans({...})` with features and limits (seats are enforced).
228
+ On Postgres, migrations add row-level security, so raw SQL is limited to
229
+ the organization too. See [Teams](docs/30-teams.md).
230
+ - **`THREAT_MODEL.md`**: every threat, its mitigation and the test that
231
+ proves it (a test keeps the references current). CI fuzzes the RPC and
232
+ form parsers with Hypothesis and runs an OWASP ZAP baseline scan.
233
+ - **CSP violation reports** go to `/__pyweb/csp-report`: a `pyweb.csp` log
234
+ line and the `pyweb_csp_violations_total` metric.
235
+
236
+ ### Changed
237
+ - **About 2.5× more requests per second** over `pyweb serve`: pages render
238
+ on the thread that read the request (the time limit moved into the
239
+ connection handler), each page's markup compiles to one Python function,
240
+ and request ids no longer cost a system call each.
241
+ - **`import pyweb` takes 2 ms** instead of 87 ms, and `pyweb --version`
242
+ about a third of the time it did: modules load when first used.
243
+ - **The sign-in, account, organization and admin pages use the UI kit**
244
+ and the app's theme: `App(theme=...)` restyles them along with your
245
+ pages. `/__pyweb/auth/auth.css` now only lays them out.
246
+ - **The templates and examples use the UI kit and motion.** `todo` rows
247
+ animate in and out and filter with a toggle group; `counter` counts;
248
+ `saas` (Teamboard) has a sidebar layout with an account menu, a
249
+ new-project dialog, toasts, animated live tasks, empty states and the
250
+ zinc theme; the dashboard's totals count up and it adds the kit's bar
251
+ chart and a sparkline. The shared `static/app.css` only lays pages out.
252
+ - **The `saas` template (Teamboard) has teams.** Projects and tasks are
253
+ `tenant=True` Models shared by an organization; each new user gets a
254
+ team, and the sidebar links to members and invitations and to API
255
+ tokens. Creating a project publishes a `project.created` webhook, and
256
+ the app is installable (`pwa=True`). Its tests show two teams kept apart
257
+ and two teammates sharing projects.
258
+ - `pyweb check` no longer asks for a policy on `tenant=True` Models: the
259
+ organization scope already limits who reads them.
260
+ - The CI benchmark measures fixed copies of the todo and saas apps
261
+ (`bench/apps/`), so redesigning an example doesn't read as a speed
262
+ change.
263
+ - The website's live demos are what the server sends (with the kit's and
264
+ motion's files), and the playground runs apps that use the kit.
265
+ - Tracebacks from `.pyweb` files show the source lines.
266
+ - `pyweb check` reports the line of a Python syntax error.
267
+ - **`pyweb.platform` is real and supported:** `platform.desktop`,
268
+ `platform.pwa`, `platform.installed` and `platform.web` are read per
269
+ request. `platform.mobile` and `platform.edge` are always False (they
270
+ were placeholders); `pyweb upgrade` points at them.
271
+
272
+ ### Fixed
273
+ - A server function argument annotated `date`, `datetime`, `time`,
274
+ `Decimal` or `UUID` arrived as the text the browser sent, unchecked
275
+ (`"banana"` for a `date`). It's now parsed (ISO 8601 for dates and
276
+ times), and anything else is a `validation_error` naming the field.
277
+ - `checked={row["done"]}` with a database's `0` rendered `checked="0"`, so
278
+ the server showed every box ticked. Boolean attributes (`checked`,
279
+ `disabled`, `selected`, ...) now follow truthiness on the server as in
280
+ the browser.
281
+ - A page using only components copied with `pyweb ui add` didn't get the
282
+ kit's stylesheet and behaviours.
283
+ - An alert's title was smaller than its text.
284
+ - The plant shop example's link and dark-mode button colours were below
285
+ WCAG contrast.
286
+ - A `Card` (or `Alert`, `Table`, `Breadcrumb`) inside `Tabs` had its
287
+ content taken for a tab panel and hidden; and the card's header styles
288
+ reached a data table's header row inside it.
289
+ - The saas template's daily digest called a query method that doesn't
290
+ exist (`values_list`); it uses `pluck`, and a test runs it.
291
+ - A menubar arrow key pressed right after opening a menu (before focus
292
+ moved into it) left that menu open.
293
+ - `lambda n=n: go(n)` in an event handler kept the event instead of `n`.
294
+ - Hydrating a page no longer rewrites attributes the server already
295
+ rendered, so it can't undo what a UI kit script (or the user, typing
296
+ early) changed in the meantime.
297
+ - Markup right after an `if`/`for` block inside an element (a
298
+ `{children}` or text line at the block's indent) was put inside the
299
+ block; it now follows it.
300
+ - An optional callback prop that wasn't passed no longer throws when its
301
+ event fires.
302
+ - Markup right after a `def` whose signature spans several lines failed to
303
+ compile.
304
+ - Text children re-rendered in the browser showed as a list (`['Medium']`).
305
+ - `get_or_404()` inside a server function answered 500; it now answers 404.
306
+ - `Post.id` (and other columns added automatically) failed before the Model
307
+ had been used once.
308
+ - Log redaction took time quadratic in the length of a word without `=`
309
+ (0.3 ms for a 64-character id, on every logged query parameter while
310
+ developing); it is now linear. Found by the new benchmark gate.
311
+ - Form submits with more than 1,000 fields or 100 files are refused (400)
312
+ instead of being parsed.
313
+ - A Model that fails to set up no longer breaks every Model defined after it.
314
+ - Models on Python 3.14+ (annotations are evaluated lazily there, PEP 649).
315
+
316
+ ## [0.5.0]
317
+
318
+ Production apps in one Python file: Models with migrations, forms, an
319
+ auth kit with an admin, row-level live updates over WebSockets on PyWeb's
320
+ own server, durable background jobs and schedules, one-command
321
+ deployment for six targets, built-in observability, and editors and AI
322
+ assistants that understand your data. Most 0.4 apps run unchanged; see
323
+ [Upgrading to 0.5](docs/27-upgrading.md) and run `pyweb upgrade --check`.
324
+
325
+ ### Added
326
+ - **Models on `pyweb.db`.** `class Post(Model): title: str = Field(max=120)`
327
+ works on SQLite, Postgres and MySQL with the same code. Types include
328
+ `Decimal`, timezone-aware `datetime`, `date`, `time`, JSON (`dict`/`list`),
329
+ `bytes`, `UUID`, `Text`, `Email`, `URL` and `Slug`. `Field(...)` holds the
330
+ column's schema and its validation rules (`min`, `max`, `pattern`,
331
+ `choices`, `format`, `unique`, `index`, `private`, `auto_now_add`, ...);
332
+ `@validates("field")` adds custom checks. Saving writes only changed
333
+ columns; unique violations become field errors.
334
+ - **Relations.** `author: User` is a foreign key, `tags: list[Tag]` is
335
+ many-to-many, `OneToOne(...)`, with reverse accessors (`user.posts`).
336
+ `include("author", "tags", "author.profile")` loads related rows with one
337
+ query per relation; reading a relation that wasn't included is an error
338
+ while developing (so pages can't do N+1 queries) and a logged warning in
339
+ production. Relation filters (`Post.author.has(...)`, `User.posts.any(...)`)
340
+ use subqueries.
341
+ - **Query builder.** Immutable, lazy queries with `Post.views > 10`, `&`,
342
+ `|`, `~`, keyword lookups (`title__icontains=`, `id__in=`, ...),
343
+ `order`, slicing, `first/last/get/get_or_404`, `count/exists`,
344
+ `values/pluck`, `aggregate` and `group_by` with `Count/Sum/Avg/Min/Max`,
345
+ bulk `update/delete`, cursor pagination with `page()`, `get_or_create`,
346
+ `upsert`, `bulk_create`, and `.live()`.
347
+ - **Transactions per request.** Every `@server` call is one transaction,
348
+ started at its first write; `@atomic` / `@atomic(False)`. Nested
349
+ `db.transaction()` blocks are savepoints. `ValidationError` from an RPC
350
+ answers 422 with the errors per field.
351
+ - **Migrations.** `pyweb db diff` compares Models with the live database
352
+ and writes Python migrations; `upgrade`, `downgrade`, `status`, `adopt`,
353
+ `squash`, `seed`. Changes are split into *expand* steps (safe during a
354
+ rolling deploy) and *contract* steps (`upgrade --contract`), including
355
+ renames with `--rename table.old=new`. A lock shared by every server
356
+ (Postgres advisory lock, MySQL `GET_LOCK`, or a lock row) makes
357
+ `pyweb serve --migrate` safe with many servers. SQLite schema changes
358
+ rebuild tables with foreign keys paused. `pyweb dev` applies pending
359
+ migrations, and apps without migrations create tables on first use.
360
+ - **Forms.** `<Form action={save_post}>` with `<Input>`, `<Textarea>`,
361
+ `<Select>`, `<Checkbox>`, `<FileInput>`, `<Submit>` and `<FormError>`
362
+ builds labelled, accessible fields from the action's Model (or plain
363
+ typed parameters). Rules are checked in the browser while typing, with
364
+ the same messages as the server; the form submits without a page load and
365
+ shows server errors next to each input, and still works as a plain POST
366
+ without JavaScript (422 re-render with values, 303 on success). CSRF
367
+ token, cross-site refusal, a one-time key per submit (double clicks run
368
+ once), only the action's own fields are read, `values={row}` edits a row
369
+ through a signed id, and `forms.carry()`/`restore()` for multi-step forms.
370
+ The browser code (`forms.js`) only loads on pages with a form.
371
+ - **Uploads and storage.** `File(types=["image/*"], max_size="2MB")` fields
372
+ and `UploadedFile` parameters; types checked from the file's bytes;
373
+ `PYWEB_MAX_UPLOAD` (default 10MB); `PYWEB_STORAGE` is a local folder
374
+ (served with a sandboxing CSP) or S3-compatible storage with presigned
375
+ URLs and no SDK.
376
+ - **Auth kit.** `auth = app.use_auth()` adds `/signup`, `/login`,
377
+ `/logout`, `/reset`, `/account` and `/admin`, rendered on the server and
378
+ working without JavaScript. Sign in with a password, an emailed link, a
379
+ passkey (also offered in the email field's autofill) or GitHub / Google /
380
+ Microsoft (PKCE + `state`), with optional authenticator-app codes and
381
+ recovery codes. Define a page at the same route to replace one;
382
+ `auth.user()`, `auth.authenticate()`, `auth.login()`, `auth.set_roles()`,
383
+ `auth.revoke()` ... for custom flows. Tables `users`, `auth_identities`,
384
+ `auth_credentials`, `auth_tokens` and `auth_events` are ordinary Models.
385
+ - **Guards.** `login=True`, `roles=[...]` and `fresh=seconds` on
386
+ `@app.page`, `@app.layout` and `@server`.
387
+ - **Row policies.** `Note.policy(read=..., write=...)` limits the rows each
388
+ user can see and change, on every query and save in a request
389
+ (`with system():` skips them on purpose).
390
+ - **Admin.** `/admin` lists, searches, edits and deletes rows of every
391
+ Model for users with the `admin` role; private fields never appear; every
392
+ change is in the audit log.
393
+ - **Account security.** scrypt (or argon2id with `pyweb-stack[auth]`)
394
+ hashes upgraded at sign-in; breached-password check in production;
395
+ guessing slowed down per account and per IP (shared through Redis) with no
396
+ lockout; the same answer and timing for unknown emails; emailed links
397
+ hashed, single-use (also under concurrent clicks) and short-lived;
398
+ passkey challenges single-use on every server; password resets, password
399
+ and role changes sign out other sessions, durably in the `users` table
400
+ (and fail closed if the database can't be checked); emailed links are
401
+ built from `PYWEB_ORIGIN`, never from the `Host` header, in production.
402
+ - **`pyweb.mail`.** `PYWEB_MAIL_URL` (SMTP/SMTPS) and `PYWEB_MAIL_FROM`;
403
+ while developing, emails are printed and kept in `pyweb.mail.OUTBOX`.
404
+ - `pyweb check --production` reports `use_auth()` without `PYWEB_ORIGIN`.
405
+ - **PyWeb's own server.** `pyweb serve` and `pyweb dev` now run on
406
+ `pyweb.net`: HTTP/1.1, WebSockets (RFC 6455) and Server-Sent Events on
407
+ one asyncio loop per process, standard library only. Thousands of open
408
+ live connections cost a coroutine each instead of a thread (the old
409
+ server stopped at 256); page renders and RPCs run on a thread pool
410
+ (`PYWEB_THREADS`). `--workers N` / `WEB_CONCURRENCY` forks processes
411
+ sharing the port and replaces crashed ones. The parser refuses request
412
+ smuggling vectors (CL+TE, repeated lengths, folded headers, bad chunk
413
+ sizes, bare LF) and has timeouts for heads, bodies, idle keep-alive and
414
+ slow readers; SIGTERM drains gracefully.
415
+ - **One WebSocket per tab for live updates.** Every `subscribe()` and live
416
+ query on a page shares `/__pyweb/ws` (Server-Sent Events used one
417
+ connection each, and browsers allow six per site), with jittered
418
+ reconnects that resume where they stopped, and automatic fallback to
419
+ Server-Sent Events, then polling. The socket checks `Origin`
420
+ (`PYWEB_ALLOWED_ORIGINS` for others), re-checks the session every minute
421
+ and closes when it's revoked, limits subscriptions, message size and
422
+ rate, and doesn't buffer without limit for slow clients. Works in the
423
+ ASGI adapter too.
424
+ - **Live queries send patches.** Rows with an `id` (or `key=`) travel as
425
+ inserts, updates, deletes and moves: changing one row of a long list
426
+ sends that row only, and the browser redraws only it. A page that missed
427
+ a change gets the whole result once. `Model.query().live()` uses the
428
+ primary key.
429
+ - **Presence.** `presence(name)` and `join(room, info, on_members,
430
+ on_cast)`: who is on a page, and ephemeral casts (cursors, typing) to
431
+ everyone else in the room, across processes with Redis.
432
+ - Feeds made while someone is signed in only work for that session.
433
+ - `live.js`: live-update code loads only on pages that use it.
434
+ - **Durable background jobs.** `@app.job(retries=, timeout=, queue=,
435
+ unique_for=)` and `fn.enqueue(..., delay=, at=, key=)`. Jobs are stored
436
+ in the app's database (`pyweb_jobs`), Redis (`PYWEB_JOBS=redis`) or memory,
437
+ and queued inside the request's transaction (a rollback queues nothing).
438
+ Workers claim with leases (`FOR UPDATE SKIP LOCKED` on Postgres/MySQL), so
439
+ a crashed or killed worker's job runs again elsewhere; failures retry with
440
+ jittered exponential backoff, then go `dead` (retry from `/admin` or
441
+ `pyweb jobs retry`); timeouts; graceful SIGTERM hands running jobs back.
442
+ `pyweb worker`, `pyweb jobs list|retry|purge|run`, and a worker inside
443
+ `pyweb dev`/`pyweb serve` (`PYWEB_WORKER=0` to run them separately).
444
+ - **Schedules.** `@app.cron("0 3 * * *", tz=..., catchup=...)` with PyWeb's
445
+ own cron parser (daylight saving handled) and `@app.every(minutes=5)`.
446
+ Every server runs the scheduler and each slot is queued exactly once,
447
+ with no leader election.
448
+ - **Email outbox.** With durable jobs, `pyweb.mail.send` queues delivery as
449
+ a retried job that only leaves if the request committed.
450
+ - **Nightly cleanup** of finished jobs, expired sign-in links and old audit
451
+ entries.
452
+ - `db.after_commit(fn)`: run code once the current transaction commits
453
+ (dropped on rollback, including a rolled-back savepoint), and
454
+ `db.after_end(fn)` for code that must run either way.
455
+ - Jobs stored in the app's database are marked done in the job's own
456
+ transaction, so a job's database writes happen exactly once even if a
457
+ worker crashes or loses its lease.
458
+ - **`pyweb deploy` plans the production topology.** It reads the app
459
+ (database, jobs, live updates, sign-in, uploads, migrations), decides
460
+ what production needs for the target and number of machines (Postgres
461
+ instead of SQLite for several machines or disposable disks, Redis for
462
+ more than one process, a separate worker, migrations once per deploy,
463
+ object storage for uploads, the secrets to set), prints the plan with
464
+ the reason for each decision, and writes the files. Targets: `docker`,
465
+ `compose` (Caddy, load-balanced replicas, Postgres, Redis, worker,
466
+ migrate step), `k8s` (web and worker Deployments, migrate init
467
+ container, probes, PodDisruptionBudget, HorizontalPodAutoscaler,
468
+ Ingress), `fly`, `render` and `railway`. `--plan` prints only;
469
+ `--check` fails CI when the plan has warnings.
470
+ - **A production image**: multi-stage, non-root, cached pip installs,
471
+ health check, `SIGTERM` drain, `PYTHON_IMAGE`/`PYWEB_SPEC`/
472
+ `EXTRA_PACKAGES` build arguments.
473
+ - `DATABASE_URL` overrides the URL in `App(database=...)` (develop on
474
+ SQLite, deploy on Postgres, same code). `pyweb serve` uses `$PORT`.
475
+ - `pyweb check --production` also reports Models without migrations,
476
+ unapplied migrations, email without `PYWEB_MAIL_URL`, uploads without
477
+ object storage, and jobs with `PYWEB_WORKER=0`.
478
+ - CI deploys a real stack (2 replicas, worker, Postgres, Redis, Caddy)
479
+ with `pyweb deploy compose` and checks load balancing, jobs on the
480
+ worker and live updates across replicas.
481
+ - **Read replicas** with `DATABASE_REPLICA_URL` (read-your-writes inside a
482
+ request), slow query logging with query plans (`PYWEB_SLOW_QUERY_MS`),
483
+ `pyweb.db.QUERY_HOOKS`, seeds (`pyweb.db.seeds`) and
484
+ `pyweb.testing.Factory`.
485
+
486
+ - **Observability.** A dev toolbar on every page in `pyweb dev`: the SQL each
487
+ request ran (repeated statements grouped, with an N+1 warning), spans,
488
+ jobs queued, emails captured, errors, and a list of every request since;
489
+ it never ships in production builds. JSON logs in production (readable
490
+ text while developing) where every line carries the request id, trace
491
+ id, route and user, and secrets (`password`, `token`, `api_key`, ... plus
492
+ `PYWEB_LOG_REDACT`) are redacted, including inside messages and URLs.
493
+ Prometheus `/metrics` (`PYWEB_METRICS_TOKEN`; requests, server functions,
494
+ queries, jobs, live sockets, mail, sign-ins, errors), added up across
495
+ `--workers` processes; `pyweb worker --metrics-port`. W3C `traceparent`
496
+ is continued, returned on every response and carried into the jobs a
497
+ request queues, so one trace covers the click and the job's work on
498
+ another machine. OpenTelemetry spans for requests, queries, jobs and
499
+ `with span("..."):` blocks (`pip install pyweb-stack[otel]`,
500
+ `PYWEB_OTEL=1`). `@app.on_error` hooks receive every unhandled error with
501
+ its request context.
502
+ - `pyweb worker --alive`: a health check for worker containers (the worker
503
+ touches a heartbeat file after each round that reached its job store);
504
+ compose and Kubernetes use it.
505
+
506
+ - **`pyweb new --template saas`**: accounts, projects and tasks with row
507
+ policies, `<Form>`s, live pages, a background job, a weekday cron email,
508
+ `/admin`, seeds, tests and a first migration, ready for `pyweb deploy`.
509
+ Templates with a database get `migrations/0001_initial.py` and a README.
510
+ - `TestClient.login(email, roles=[...])` signs a test in (creating the
511
+ account with the auth kit) without going through the sign-in form;
512
+ `logout()` signs out.
513
+ - `seeds.py` sees the app's Models by name; `pyweb.models.model("Post")`
514
+ looks one up anywhere.
515
+ - A server function parameter can take its rules from a default:
516
+ `title: str = Field(min=1, max=120)` (same as `Annotated[str, Field(...)]`).
517
+ - `<Input name="project_id" type="hidden" />` is just the input (for ids the
518
+ page sets with `values=`); `label=""` hides a field's label but keeps it as
519
+ the input's `aria-label`.
520
+
521
+ - **Editors know your data.** The language server completes Model fields
522
+ and query methods after `Post.`, keyword fields in `where(`/`create(`,
523
+ relations in `include("`, columns in `order("`, `Field(` options and a
524
+ `<Form>`'s fields in `name="`. It flags form fields the action doesn't
525
+ have, loops that read a relation their query didn't `include()` (N+1,
526
+ pointing at the line), and Models changed without a migration, with a
527
+ code lens that writes it. VS Code: "Create Migration from Model Changes",
528
+ "Apply Migrations", and snippets for Models, relations, form pages, live
529
+ pages, jobs, schedules, row policies and `use_auth`. The extension uses
530
+ the current language client (vscode-languageclient 10), is bundled
531
+ (12 files instead of 364), needs VS Code 1.91 or later, and is tested in
532
+ a real VS Code against the real language server in CI.
533
+ - **`pyweb db check`**: fails (exit 1) when the Models changed without a
534
+ migration, by applying the migrations to a scratch database, so it needs
535
+ no real database. For CI.
536
+ - **MCP tools for data**: `pyweb_db_schema` (Models, tables, drift),
537
+ `pyweb_db_query` (read-only SQL, capped, secrets redacted),
538
+ `pyweb_migrations` (status, diff, upgrade), `pyweb_jobs` (list, retry,
539
+ run) and `pyweb_requests` (each request's SQL, N+1 warnings, jobs, errors).
540
+
541
+ - **Playground 2.** Several files per app (import components from a second
542
+ `.pyweb` file; shared as one link, downloaded as a zip), a Requests tab
543
+ (each request's SQL with N+1 warnings, spans, jobs, emails, errors), a
544
+ Database tab (tables and rows, secrets redacted), a visitor picker to see
545
+ pages signed out, signed in or as an admin, background jobs run right
546
+ after the request that queued them, and SQLite loaded when an app has a
547
+ database. New examples: the blog (on Models) and the saas template.
548
+ - `Worker.run_inline()` runs due jobs on the calling thread (no thread pool).
549
+
550
+ - **`pyweb upgrade --check/--fix`** lists what an app needs to change for
551
+ 0.5, with file and line, and applies the safe rewrites:
552
+ `Model.configure(...)` → `App(database=...)`, and `pyweb db
553
+ migrate/rollback` and `pyweb deploy --target X` in Procfiles, scripts,
554
+ Dockerfiles and CI files.
555
+ - `PYWEB_STRICT_DEPRECATIONS=1` turns deprecation warnings into errors.
556
+ Deprecated in 0.5: `Model.configure()`, the in-memory `Queue`,
557
+ `RedisQueue` and `@task` (use `@app.job`), and `pyweb deploy
558
+ --target/--compose`.
559
+ - `pyweb check` warns about SQL built from strings passed to `execute()`,
560
+ pages that read `auth.user().x` without `login=True`, and Models owned by
561
+ a user without a row policy; `--strict` fails on any warning.
562
+
563
+ ### Changed
564
+ - The blog template and example use a Model instead of raw SQL.
565
+ - `pyweb lsp` answers requests it doesn't handle with "method not found"
566
+ (it answered `null`), as LSP clients expect.
567
+ - `PYWEB_MAX_CONNECTIONS` defaults to 10,000 per process (it was 256 for
568
+ the threaded server, which `pyweb serve --app module:factory` still uses).
569
+ - `pyweb.testing.serve` runs PyWeb's server.
570
+ - SQLite connections enforce foreign keys and use write-ahead logging.
571
+ - `Model.configure()` is deprecated in favour of `App(database=...)` /
572
+ `DATABASE_URL`; 0.4-style Models keep working (rows still read like
573
+ dicts: `row["name"]`).
574
+ - In production, a Model with no database is an error instead of an
575
+ in-memory SQLite file.
576
+
577
+ ### Security
578
+ - Sessions and feed tokens are verified only with keys derived per purpose.
579
+ The raw secret, still accepted in 0.4.4 for tokens from older versions,
580
+ is no longer a key.
581
+ - The default Content-Security-Policy allows inline `<style>` blocks by
582
+ their hashes instead of `'unsafe-inline'` (so injected markup can't add
583
+ a stylesheet that leaks page content), and adds `form-action 'self'`.
584
+
585
+ ### Fixed
586
+ - Rules on server function parameters (`Annotated[str, Field(max=40)]`) were
587
+ ignored: a direct call could send any value, and forms didn't get the
588
+ matching browser checks. They're enforced on every call now, with errors
589
+ per field (422).
590
+ - `owner: User = Field(readonly=True)` made a plain column instead of a
591
+ foreign key; options given with `Field(...)` now apply to the relation.
592
+ - `pyweb db seed --database URL` wrote to the app's own database; apps
593
+ without migrations yet couldn't be seeded before `pyweb dev` created
594
+ their tables.
595
+ - `pyweb dev` didn't reload pages larger than 1 KB after a change: they were
596
+ gzipped before the reload script could be added.
597
+ - `Model.query()...live()` in a page made a static page: the compiler only
598
+ recognised `live(db, sql)`.
599
+ - A value computed from a live query (`left = len([t for t in tasks if not
600
+ t["done"]])`), or from any server-loaded list a handler changes, was
601
+ computed once on the server and never updated in the browser. It now
602
+ follows its source there.
603
+ - A form for a Model with a read-only field (`owner: User =
604
+ Field(readonly=True)`) was refused with "owner is required" before the
605
+ server function could set the field.
606
+ - Validation messages named the field twice ("body Body is required").
607
+ - Schedules in UTC (including PyWeb's nightly cleanup) no longer need a
608
+ time zone database, which Windows and Pyodide (the playground) don't
609
+ ship; other zones explain that `pip install tzdata` provides one.
610
+ - The server answered a refused connection (503) or a rejected request and
611
+ closed at once; unread request bytes then made the kernel reset the
612
+ connection, which could destroy the response. It now closes politely
613
+ (half-close, drain briefly).
614
+ - While developing, a Model's table made on first use inside a request that
615
+ then failed was rolled back but still counted as made ("no such table"
616
+ until a restart). Tables are now made when the app loads, and a lazily
617
+ made one only counts once its transaction commits.
618
+ - Audit log entries written during a request that rolls back are kept
619
+ (written when the transaction ends, committed or not), and a failing
620
+ audit insert can no longer abort the request's transaction.
621
+ - `Site(..., rate_limit=False)` (used by `pyweb.testing.serve`) passed
622
+ `False` to the server instead of turning rate limits off.
623
+
624
+ ## [0.4.4]
625
+
626
+ Security and running in production. No app changes needed, and nobody
627
+ is signed out by the upgrade.
628
+
629
+ ### Added
630
+ - **Signing keys per purpose, and rotation.** Sessions, live-update feeds
631
+ and live query specs are signed with separate keys derived from
632
+ `PYWEB_AUTH_SECRET`. `PYWEB_AUTH_SECRET_PREVIOUS` keeps tokens from an
633
+ old secret working while you change it. Tokens from 0.4.3 and earlier
634
+ still work.
635
+ - **Sessions:** each login starts a new session; sessions renew on visits
636
+ and end 30 days after sign-in; `auth.revoke_user(user_id)` signs a user
637
+ out on every device (in memory, or shared with `RedisSessionVersions`).
638
+ - **Typed server function arguments:** `list[...]`, `dict[...]`,
639
+ `Optional`, `Literal`, dataclasses, Models and `Email` are checked
640
+ against their type hints, and unknown arguments are refused (422).
641
+ - **`pyweb/config.py`:** every `PYWEB_*` setting in one place, validated
642
+ at startup (a bad value stops the server with a clear message).
643
+ - **`PYWEB_REDIS_URL`:** shares rate limits (`RedisRateLimiter`) and live
644
+ updates between server processes.
645
+ - **`/readyz`** checks that every database the app opened answers (503
646
+ when one doesn't); `/healthz` stays a liveness check.
647
+ - **Graceful shutdown:** on SIGTERM and ASGI shutdown, live connections
648
+ end and background jobs get up to 10 seconds to finish.
649
+ - **`pyweb deploy --target compose --domain example.com`** puts Caddy in
650
+ front of the app with automatic HTTPS, keeps the app reachable only
651
+ through it, and sets `PYWEB_TRUST_PROXY=1` and Secure cookies. The k8s
652
+ target trusts its Ingress and uses `/readyz` for readiness. The
653
+ deployment docs explain reverse proxies, with nginx and Caddy examples.
654
+ - **`pyweb check --production`** lists settings that would leave a gap
655
+ (secret, HTTPS cookies, proxy, Redis with several workers, ...).
656
+ - **npm supply chain:** `pyweb.lock` records a SHA-256 per vendored file
657
+ and `pyweb build` refuses files that changed.
658
+ - **CI:** dependency vulnerability audit (pip-audit), CodeQL code
659
+ scanning, Dependabot, and signed build provenance for release files.
660
+ - **A much better playground** on the website: syntax highlighting with
661
+ the error line marked (click the line number to jump to it), a Console
662
+ that shows `print()` output, every server call with its arguments,
663
+ status and time, and errors from the page; a draggable divider;
664
+ phone/tablet/desktop preview widths and a reload button; highlighted
665
+ JavaScript with its gzipped size; Reset, Download (`app.pyweb`, ready
666
+ for `pyweb dev`) and autosave, so edits survive a reload; and a
667
+ Code/Preview switch on phones.
668
+ - Docs: a threat model, key rotation, session lifetime, a production
669
+ checklist, and the new settings.
670
+
671
+ ### Security
672
+ - Requests the browser marks `Sec-Fetch-Site: cross-site` can't call
673
+ server functions.
674
+ - Deeply nested JSON bodies crashed the request handler with a
675
+ `RecursionError`; bodies deeper than 32 levels or with more than
676
+ 10,000 values are now refused.
677
+ - `pyweb serve` and `pyweb dev` drop clients that stall (30 s), answer
678
+ 503 past 256 simultaneous connections and 431 to headers over 16 KB.
679
+ - At most 20 open live connections per client address (429 beyond).
680
+ - Pages that take longer than 30 seconds to render answer 504 instead of
681
+ holding a thread.
682
+ - New response headers: `Permissions-Policy`, `Cross-Origin-Opener-Policy`,
683
+ `Cross-Origin-Resource-Policy`, and `Strict-Transport-Security` when
684
+ `PYWEB_COOKIE_SECURE` is on. Both servers now send the same set.
685
+ - `RedisCache` no longer unpickles data unless `allow_pickle=True`
686
+ (unpickling can run code written by anyone with Redis access).
687
+
688
+ ### Fixed
689
+ - `@auth_required` server functions rejected sessions older than one
690
+ hour, while pages accepted them for 7 days.
691
+
692
+ ## [0.4.3]
693
+
694
+ A review release: bug fixes, security hardening and speed-ups. No API
695
+ removals; one new setting (`PYWEB_TRUST_PROXY`).
696
+
697
+ ### Fixed
698
+ - Pages showed stale data when a module-level value (`ITEMS = []`) was
699
+ changed by an `@server` function: the compiler copied its starting
700
+ value into browser code, and the page re-rendered with it. Values
701
+ that code changes now come from the server on each request.
702
+ - `db.transaction()`: a statement error that your code caught rolled
703
+ back the transaction's earlier writes while later ones still
704
+ committed. Errors inside a transaction now leave it to
705
+ `transaction()` (all or nothing).
706
+ - `db.stream()` inside a transaction waited for a second connection
707
+ (a 10 second hang with SQLite in memory); it now uses the
708
+ transaction's.
709
+ - `pyweb serve` sent SVG, images, fonts, JSON and WebAssembly as
710
+ `application/octet-stream` (browsers don't show an SVG sent that way).
711
+ - Your own files in `static/` (such as `app.css`) were cached by
712
+ browsers for a year, so deploys didn't show up for returning
713
+ visitors. They're now revalidated with an ETag (a `304` when
714
+ unchanged); content-hashed files, `?v=` URLs and npm packages are
715
+ still cached for a year.
716
+ - Client-side navigation could show a page prefetched before a server
717
+ call changed its data.
718
+ - `async def` server functions ignored `rpc_timeout`.
719
+ - The ASGI adapter kept only the last of repeated request headers, so
720
+ HTTP/2 clients sending each cookie separately lost all but one.
721
+ - `pyweb dts`: type aliases were never written, members on one line
722
+ (`{ a: string; b?: number }`) produced invalid Python, and optional
723
+ fields before required ones made the stub fail to import.
724
+ - Very long upload filenames are shortened (keeping the extension)
725
+ instead of failing to save.
726
+
727
+ ### Security
728
+ - RPC rate limits used the `X-Forwarded-For` header, which any client
729
+ can set (so the limit was easy to get around), and put every visitor
730
+ without a session cookie in one shared bucket. They now use the
731
+ connection's address; behind a reverse proxy set `PYWEB_TRUST_PROXY=1`
732
+ (or the number of proxies) to use the address your proxy saw.
733
+ - `auth.require_session` and `auth.login_response` only redirect to
734
+ paths on your site (`//evil.example` becomes `/`); the new
735
+ `auth.safe_next()` does the same for your own `?next=` handling.
736
+ - Magic-link tokens are signed separately from session cookies, so one
737
+ can't be used as the other. Links issued before the upgrade stop
738
+ working (they last 15 minutes).
739
+
740
+ ### Performance
741
+ - HTML, JavaScript, CSS, JSON and SVG are gzipped for browsers that
742
+ accept it (the shared runtime goes from 46 KB to 15 KB); static files
743
+ are compressed once and kept.
744
+ - Memory no longer grows without bound in long-running servers: the
745
+ rate limiter forgets idle callers, the realtime bus forgets channels
746
+ quiet for an hour, the in-memory cache drops expired entries, and
747
+ the job queue forgets jobs an hour after they finish.
748
+ - The job queue runs jobs on a pool of 8 threads instead of one thread
749
+ per job, and `wait()` returns as soon as the job finishes.
750
+ - `pyweb dev` no longer re-scans virtualenvs in the project folder
751
+ every 0.4 s, and reloads when `pyweb.lock` changes (after
752
+ `pyweb add`).
753
+
754
+ ## [0.4.2]
755
+
756
+ ### Added
757
+ - VS Code extension: **PyWeb: Run App** (a run button on `.pyweb`
758
+ files, Ctrl+F5), **New App** from any template, **Check App**, **Add
759
+ npm Package**, **Install or Update PyWeb** and **Set Up AI Assistant
760
+ (MCP)**, which adds the PyWeb MCP server to `.vscode/mcp.json`.
761
+ - VS Code extension: closing tags are added as you type, Emmet works in
762
+ markup, new snippets (`layout`, `error`, `stream`, `livequery`, `npm`,
763
+ `mount`), a status item that shows the PyWeb version in use, and an
764
+ icon.
765
+
766
+ ### Fixed
767
+ - VS Code extension: the language server failed with
768
+ `spawn python ENOENT` when no Python interpreter was selected. The
769
+ extension now looks for a Python with PyWeb (the selected
770
+ interpreter, `pyweb`, `py`, `python3`, `python`), offers to install
771
+ PyWeb when it's missing, and restarts when you change interpreter.
772
+
773
+ ## [0.4.1]
774
+
775
+ ### Added
776
+ - MCP server: `pyweb_routes` maps every page (route, path and query
777
+ parameters with types and defaults, title and head tags, layouts,
778
+ live-data variables), the layouts, error pages and server functions.
779
+ - MCP server: `pyweb_packages` adds, removes or lists npm packages for
780
+ browser code, with each package's exported names and the `npm(...)`
781
+ lines to bind them.
782
+ - MCP server: `add_ai_feature` and `make_data_live` prompts.
783
+ - MCP server: `pyweb_call` returns every chunk a streaming function
784
+ yields; `pyweb_render` reports the title, head tags and the layouts that
785
+ rendered; `pyweb_check` reports layouts, live variables, npm packages
786
+ and streaming functions.
787
+ - The AI guide (`pyweb_guide`, `AGENTS.md`) has sections with working
788
+ examples for multi-page apps, streaming and AI, live data and npm
789
+ packages.
790
+ - A new README with screenshots, and docs on publishing the VS Code
791
+ extension.
792
+
793
+ ### Fixed
794
+ - Markup right after a `def` line that ends in a comment
795
+ (`def Page(q: str = ""): # ...`) failed to compile.
796
+
797
+ ## [0.4.0]
798
+
799
+ Apps that are bigger, livelier and smarter: npm packages without
800
+ Node.js, multi-page apps with layouts and client-side navigation,
801
+ streaming for AI features, and page data that follows the database.
802
+
803
+ ### Added
804
+ - **npm packages without Node.js.** `pyweb add chart.js/auto` downloads a
805
+ package from the npm registry, checks its sha512 checksum, follows its
806
+ imports from the browser entry point and copies only the files it
807
+ needs (and its dependencies) into `static/vendor/`, pinning versions in
808
+ `pyweb.lock`. `pyweb add` with no arguments reinstalls from the lock;
809
+ `pyweb remove` takes a package out. Browser code binds exports with
810
+ `Chart = npm("chart.js/auto")` or `npm("pkg", "Export")`. Calling a
811
+ class constructs it and keyword arguments become an options object.
812
+ Each page imports only the packages it uses, through an import map
813
+ that the Content Security Policy allows by hash. CommonJS-only and
814
+ Node.js-only packages are refused with an explanation, and an
815
+ uninstalled package is a compile error that names the command to run.
816
+ - **`ref={el}`** sets a page variable to an element, in time for
817
+ `on_mount`, for libraries that draw into the page.
818
+ - Changing an attribute of a package's object (`chart.value = 5`)
819
+ re-renders whatever shows it.
820
+ - Web components (custom elements from npm) work in markup.
821
+ - `pyweb lsp`: hover shows an npm binding's installed version and
822
+ TypeScript signature; completion offers installed packages in
823
+ `npm("` and a module's exports after `name.`.
824
+ - New docs page: npm packages.
825
+ - **Layouts.** `@app.layout` wraps every page (or, with
826
+ `@app.layout("/admin")`, pages under a prefix) in shared markup;
827
+ `{children}` marks where the page goes. Layouts nest, have their own
828
+ server code, state and handlers, and pages can opt out with
829
+ `layout=None` or pick one with `layout="Name"`.
830
+ - **Client-side navigation.** Links between pages fetch the next page's
831
+ HTML and swap it in below the layouts both pages share, so layout
832
+ state (an open menu, a player) survives. Links are prefetched on
833
+ hover or focus, back/forward restore the scroll position, and anything
834
+ unusual falls back to a full page load. `navigate("/path")` from
835
+ `pyweb.browser` does the same from browser code; `on_unmount` runs
836
+ when a page is left; `App(client_nav=False)` turns it off.
837
+ - **Current links.** Links to the current page get
838
+ `aria-current="page"` and links to its parent sections
839
+ `aria-current="true"`, on the server and after each navigation.
840
+ - **Head tags.** `@app.page(description=..., image=..., noindex=...)` and
841
+ `head(title=..., description=...)` in page code add description, Open
842
+ Graph and Twitter tags; `App(base_url=...)` adds canonical URLs and
843
+ absolute image links.
844
+ - **Error pages in `.pyweb`.** `@app.error(404)` / `@app.error(500)` pages
845
+ take `path`, `status`, `message` or `request_id` and use root layouts.
846
+ - **Typed query parameters.** Page parameters that aren't in the route
847
+ are read from the query string and converted by annotation (`int`,
848
+ `float`, `bool`, `list[...]`); bad or missing values answer 400.
849
+ - New docs page: Layouts & navigation.
850
+ - **Streaming server functions.** A `@server` function that `yield`s
851
+ sends each value as it's produced (NDJSON over the same RPC endpoint);
852
+ browser code reads it with `async for`. `stream.cancel()` or leaving the
853
+ loop aborts the request and closes the generator on the server, so an
854
+ upstream AI call stops too. Errors raised part-way arrive as
855
+ `RPCError`. Works with `async def` generators, under `pyweb serve`,
856
+ `pyweb dev` and ASGI, and `TestClient.rpc` returns the streamed values.
857
+ - **`<Markdown text={...} />`** (from `pyweb`) renders Markdown to safe
858
+ HTML on the server and updates it in the browser as the text changes,
859
+ with the same rules in both places. Raw HTML is shown as text and
860
+ links only accept http(s), mailto and relative URLs.
861
+ - **`ai-chat` template and example**: a streaming chat with Stop and
862
+ Markdown replies that talks to Anthropic, any OpenAI-compatible server
863
+ (OpenAI, Ollama, vLLM, ...) or a built-in demo model, chosen by
864
+ environment variables. `pyweb new NAME --template ai-chat`.
865
+ - New docs page: Building AI apps.
866
+ - **Live data.** `rows = live(db, "select ...", params)` in a page or
867
+ layout renders the rows and keeps them current: writes through
868
+ `pyweb.db` (and `pyweb.models`) announce their table after commit, each
869
+ distinct query re-runs once per change (bursts coalesced) and sends
870
+ rows to every page showing it only when they changed. Pages follow a
871
+ signed feed; `db.notify("table")` covers writes made elsewhere. With
872
+ `RedisBus` it works across processes, and any process can take over a
873
+ query from the page's signed description.
874
+ - New docs page: Live data.
875
+ - **`watch(lambda: value, handler)`** (from `pyweb.browser`) runs a
876
+ handler whenever a value changes, for work outside markup such as
877
+ redrawing a chart or saving a draft.
878
+ - `pyweb add` applies packages' `browser` field (browser versions of
879
+ files, modules turned off), so packages such as ethers install.
880
+ - New examples: **dashboard** (live queries and a Chart.js chart from
881
+ npm), **site** (layouts, navigation, query parameters, a 404 page) and
882
+ **ai-chat**. The playground has the site and AI chat examples.
883
+ - New docs page: Recipe: wallets & web3 (wallet sign-in with ethers and
884
+ eth-account).
885
+ - A layout's `{children}` can sit on a line of its own.
886
+
887
+ ### Fixed
888
+ - Subscriptions and cleanups set up in `on_mount` now end when the page
889
+ is left (they were never stopped).
890
+
891
+ ### Changed
892
+ - The experimental `pyweb.live` module (in-memory live tables) is now
893
+ `pyweb.livetable`; `pyweb.live` is the new live-query function.
894
+ - `pyweb.npm` (TypeScript declarations to dataclasses) is now
895
+ `pyweb.dts`, and its command is `pyweb dts`. `pyweb build` no longer
896
+ writes an esm.sh import map; packages come from `pyweb.lock`.
897
+ - The default Content Security Policy adds `worker-src 'self' blob:` so
898
+ packages can start Web Workers.
899
+ - The starter stylesheet styles Markdown and chat bubbles.
900
+ - The browser runtime is about 14 KB gzipped (was about 11 KB), for
901
+ client-side navigation and npm support.
902
+ - New website design: a colour system where blue means the browser and
903
+ amber the server, self-hosted Inter / Bricolage Grotesque / JetBrains
904
+ Mono, dark code windows, and a landing page that shows each line of an
905
+ app with where it runs (worked out by the compiler) next to the real
906
+ compiled output.
907
+ - The example apps and the `pyweb new` stylesheet use the new palette.
908
+
909
+ ## [0.3.0]
910
+
911
+ Faster pages that feel like an app, apps that span several files, and
912
+ tools for editors, the browser and AI assistants.
913
+
914
+ ### Added
915
+ - **Hydration.** Pages now adopt the server-rendered DOM instead of
916
+ rebuilding it: nodes are kept, focus and text typed before the script
917
+ loads survive, and typed values reach their variables. When the server
918
+ HTML doesn't match, the page falls back to a client render and logs
919
+ a warning. The page root carries `data-pw-mode="hydrated"` or
920
+ `"rendered"`.
921
+ - **Live updates.** `publish(name, data)` in server code pushes to every
922
+ page that called `subscribe(channel_feed, handler)`, over Server-Sent
923
+ Events with a polling fallback. Feeds come from `channel(name)` while
924
+ rendering: signed, expiring, and positioned so no message published
925
+ after the render is missed. `pyweb dev`, `pyweb serve` and the ASGI
926
+ adapter stream; `realtime.use_bus()` shares the bus (e.g. Redis)
927
+ between processes. The chat example uses it instead of polling.
928
+ - **Multi-file apps.** `from widgets import Card, save, COLOR` imports
929
+ components, `@server` functions and constants from `widgets.pyweb` (or
930
+ `ui/cards.pyweb` as `ui.cards`). Each file keeps its own names in the
931
+ generated JavaScript, imported files run as real modules on the
932
+ server, `pyweb build` copies them into `dist/`, and errors name the
933
+ file they're in. Pages stay in the app file; circular imports and
934
+ duplicate server-function names are compile errors.
935
+ - **Editor support.** `pyweb lsp` is a language server (stdio, standard
936
+ library only): compile errors and security warnings as you type,
937
+ hover showing where each name runs and why, completion for components,
938
+ props and tags, go to definition across files, and an outline. The new
939
+ VS Code extension (`editors/vscode`) adds highlighting and snippets and
940
+ starts it; releases attach a `.vsix`. Setup for Neovim and Helix is in
941
+ the command-line docs.
942
+ - **Playground** on the website: edit an app and run it in the browser,
943
+ with the real compiler, server rendering, `@server` functions,
944
+ sessions and live updates running on Pyodide. Shows the compiled
945
+ JavaScript and what runs where, reports errors with a fix, and shares
946
+ code as links.
947
+ - **MCP tools** `pyweb_screenshot` (open a page in headless Chromium,
948
+ run click/fill/press steps, get a screenshot, the page text, console
949
+ errors and hydration status) and `pyweb_test` (run the app's pytest
950
+ tests and report failures). New apps include a starter `test_app.py`.
951
+ - `python -m pyweb.bench --max-ssr-ms N --max-compile-ms N` fails when
952
+ a benchmark exceeds its budget; CI runs it.
953
+
954
+ ### Removed
955
+ - The old `pyweb.lsp` helper functions (`complete`, `DocumentState`,
956
+ `complete_*`, `diagnostics_for_compile_error`, `hover(compiled, name)`,
957
+ `boundary_lens`); they described an earlier API and no editor used them.
958
+
959
+ ### Changed
960
+ - `import pyweb` no longer needs the `sqlite3` module (it's imported when
961
+ a SQLite database is opened), and password hashing falls back to a
962
+ pure-Python PBKDF2 on Pythons without OpenSSL. Both make PyWeb run on
963
+ Pyodide.
964
+ - `/__pyweb/events` and `/__pyweb/poll` require a signed `?feed=`; the
965
+ unauthenticated `?channel=` form is gone, so channels are no longer
966
+ readable by anyone who guesses a name.
967
+ - Server rendering caches compiled template expressions; rendering a
968
+ page is about twice as fast.
969
+ - Generated page modules pass element children as a function and wrap
970
+ every child in a call (`$t`, `$dyn`), so nodes are created in document
971
+ order.
972
+
973
+ ## [0.2.0]
974
+
975
+ Tools for building PyWeb apps with AI assistants.
976
+
977
+ ### Added
978
+ - `pyweb mcp`: a Model Context Protocol server (stdio, standard library
979
+ only) for Claude Code, Cursor, Claude Desktop, VS Code and other MCP
980
+ clients. Tools: `pyweb_guide`, `pyweb_new_app`, `pyweb_check` (errors
981
+ with line numbers and fix hints), `pyweb_inspect`, `pyweb_compiled`,
982
+ `pyweb_render` and `pyweb_call` (session cookies persist between calls);
983
+ resources for the guide and templates; a `build_pyweb_app` prompt.
984
+ Tested against the official MCP Python SDK.
985
+ - An AI-oriented guide to writing `.pyweb` apps, shipped in the package.
986
+ - `pyweb new --template blank|counter|todo|blog|auth|chat`; new projects
987
+ include `AGENTS.md` and `CLAUDE.md` for coding agents.
988
+ - The docs site publishes `llms.txt` and `llms-full.txt`, and has a new
989
+ "AI assistants & MCP" page.
990
+
991
+ ## [0.1.0]
992
+
993
+ The first public release, published on PyPI as `pyweb-stack`. The
994
+ unpublished prototype was labelled "1.0" but only the simplest counter
995
+ pattern worked in a browser; this release rebuilds the compiler, runtime
996
+ and server around a design that works end to end, and is verified in a
997
+ real browser.
998
+
999
+ ### Language and compiler
1000
+ - `.pyweb` parser with a character scanner: tags and expressions may span
1001
+ lines, braces and strings nest inside `{...}`, `elif` chains, HTML
1002
+ comments, and exact line numbers (the Python half keeps the source's
1003
+ line count; this also fixes crashes on Python 3.10/3.11).
1004
+ - Python → JavaScript translation of a documented subset with Python
1005
+ semantics (truthiness, `==` on containers, negative indexing, floor
1006
+ division/modulo, string/list/dict/set methods, Python exceptions).
1007
+ Unsupported code is a compile error with `file:line`. Verified
1008
+ differentially against CPython.
1009
+ - State inference: signals (mutated by handlers or bound), computeds,
1010
+ constants; initial values from literals, the browser, or the server.
1011
+ Mutations (`append`, item assignment, `del`, …) are copy-on-write.
1012
+ - Components with props, defaults and `children`; `on_mount` hook;
1013
+ page titles; `App(title=, stylesheets=, lang=)`.
1014
+ - `@server` calls from handlers compile to awaited typed RPC; async
1015
+ propagates through handler calls.
1016
+ - Only values browser code reads are serialised; values derived from
1017
+ server data are computed on the server. Secret-looking names read by
1018
+ browser code are rejected.
1019
+
1020
+ ### Runtime
1021
+ - New browser runtime: dependency-tracked signals, cached computeds,
1022
+ owned effects with disposal, batching; keyed lists and conditionals in
1023
+ marker-bounded regions; two-way binding for text, number, checkbox,
1024
+ radio and select; `javascript:` URL blocking; RPC client with typed
1025
+ errors.
1026
+
1027
+ ### Server
1028
+ - Pages render per request with real server values; `request`,
1029
+ `session`, `redirect`, `NotFound`; typed route parameters.
1030
+ - `@server` functions are registered automatically in `dev`, `serve`,
1031
+ tests and ASGI.
1032
+ - New ASGI adapter: `pyweb.asgi.create_app`.
1033
+ - RPC: JSON-only and same-origin checks, rate limiting, timeouts that
1034
+ actually return on time, async functions, JSON conversion for
1035
+ dataclasses/datetimes/Decimals.
1036
+ - Content-Security-Policy and security headers on HTML responses.
1037
+
1038
+ ### Tooling
1039
+ - `pyweb dev`: live reload and an in-browser compile-error overlay.
1040
+ - `pyweb build` produces a self-contained `dist/` (source, manifest with
1041
+ gzip sizes, hashed assets, working Dockerfile); `pyweb serve` runs it.
1042
+ - Token-aware JS minifier (the previous one altered string literals).
1043
+ - `pyweb.testing.TestClient` and `pyweb.testing.serve` for tests.
1044
+ - Clean `file:line: message` compile errors from the CLI.
1045
+
1046
+ ### Data, auth, jobs
1047
+ - `?` placeholders work on Postgres and MySQL; lazy connection pools.
1048
+ - WebAuthn: challenge, origin and sign-count checks; DER signatures; uses
1049
+ `cryptography` when installed.
1050
+ - Redis bus and queue work against real Redis.
1051
+
1052
+ ### Fixed
1053
+ - Migrations: the `pyweb_migrations` journal table can now be created on
1054
+ MySQL (`VARCHAR(255)` key instead of `TEXT`).
1055
+ - `pyweb serve` crashed on startup (logger misconfiguration).
1056
+ - Production builds referenced a hashed runtime the page modules never
1057
+ imported.
1058
+ - Installed packages were missing the browser runtime file.
1059
+ - `transaction()` did not roll back on Postgres/MySQL pools.
1060
+ - `sqlite:///file.db` pointed at the filesystem root.
1061
+ - `RedisBus.since()` duplicated and mis-ordered messages;
1062
+ `RedisQueue.drain(timeout=0)` blocked forever.
1063
+ - Jobs raising `TypeError` ran twice.
1064
+ - `Query.order_by()` ignored `-field` and did not validate a single field.
1065
+ - The deploy Dockerfile ran a non-existent `serve --dir` flag.
1066
+
1067
+ ### Removed
1068
+ - Modules the framework no longer uses: `pyweb.components` (pure-Python
1069
+ element API), `pyweb.routing` (unused router), `pyweb.reactive`
1070
+ (server-side `Signal`/`Computed`/`Effect`/`live`), and
1071
+ `pyweb.compiler.placement` (superseded by the compiler's boundary checks).
1072
+ - The `@browser` and `@shared` decorators, which had no effect.
1073
+ - `pyweb.compiler.rpc.client_stub`/`server_handler` and unused AST classes.
1074
+ - `docs/BUGLOG.md` (it described the prototype's internals) and the committed
1075
+ `website/dist/`; the Pages workflow builds the site.
1076
+ - The regex-based codegen, the virtual test client that only recorded
1077
+ clicks, documentation "snippets" that did not use PyWeb, island and
1078
+ streaming-SSR string helpers without runtime support, and the no-op
1079
+ `--security-scan` flag.
1080
+
1081
+ ## Pre-release prototype (unpublished)
1082
+ - Initial prototype.