pyweb-stack 0.6.0__tar.gz → 0.6.1__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 (354) hide show
  1. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/CHANGELOG.md +53 -0
  2. {pyweb_stack-0.6.0/pyweb_stack.egg-info → pyweb_stack-0.6.1}/PKG-INFO +3 -3
  3. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/README.md +2 -2
  4. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/02-quickstart.md +6 -0
  5. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/14-cli.md +35 -2
  6. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/16-limitations-roadmap.md +8 -4
  7. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/17-ai-assistants.md +2 -1
  8. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/27-upgrading.md +1 -0
  9. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/29-performance.md +11 -1
  10. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyproject.toml +2 -2
  11. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/app_loader.py +26 -7
  12. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/bench.py +36 -1
  13. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/cli/__init__.py +79 -32
  14. pyweb_stack-0.6.1/pyweb/compiler/errors.py +223 -0
  15. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/lower.py +10 -1
  16. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/pipeline.py +51 -1
  17. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/pyjs.py +17 -3
  18. pyweb_stack-0.6.1/pyweb/doctor.py +303 -0
  19. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/lsp_ui.py +19 -2
  20. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/mcp.py +12 -49
  21. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/mcp_tools.py +22 -0
  22. pyweb_stack-0.6.1/pyweb/runtime/browser/dev/reload.js +102 -0
  23. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/runtime.js +2 -1
  24. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/server/__init__.py +2 -2
  25. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/extending.py +7 -0
  26. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/upgrade.py +12 -1
  27. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1/pyweb_stack.egg-info}/PKG-INFO +3 -3
  28. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/SOURCES.txt +7 -0
  29. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_auth.py +2 -1
  30. pyweb_stack-0.6.1/tests/test_cli_test.py +44 -0
  31. pyweb_stack-0.6.1/tests/test_compile_cache.py +88 -0
  32. pyweb_stack-0.6.1/tests/test_dev_reload.py +53 -0
  33. pyweb_stack-0.6.1/tests/test_doctor.py +128 -0
  34. pyweb_stack-0.6.1/tests/test_error_messages.py +164 -0
  35. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_forms_v5.py +2 -1
  36. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_mcp.py +3 -1
  37. pyweb_stack-0.6.0/pyweb/compiler/errors.py +0 -29
  38. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/LICENSE +0 -0
  39. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/MANIFEST.in +0 -0
  40. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/THREAT_MODEL.md +0 -0
  41. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/01-introduction.md +0 -0
  42. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/03-tutorial.md +0 -0
  43. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/04-pyweb-files.md +0 -0
  44. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/05-reactivity.md +0 -0
  45. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/06-server-functions.md +0 -0
  46. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/07-browser-python.md +0 -0
  47. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/08-pages-routing-assets.md +0 -0
  48. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/09-data.md +0 -0
  49. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/10-auth.md +0 -0
  50. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/11-testing.md +0 -0
  51. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/12-deployment.md +0 -0
  52. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/13-security.md +0 -0
  53. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/15-toolkit.md +0 -0
  54. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/18-npm-packages.md +0 -0
  55. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/19-layouts-navigation.md +0 -0
  56. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/20-ai-apps.md +0 -0
  57. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/21-live-data.md +0 -0
  58. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/22-recipe-web3.md +0 -0
  59. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/23-forms.md +0 -0
  60. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/24-jobs.md +0 -0
  61. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/25-scaling.md +0 -0
  62. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/26-observability.md +0 -0
  63. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/28-saas-tutorial.md +0 -0
  64. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/30-teams.md +0 -0
  65. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/31-pwa-push.md +0 -0
  66. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/32-desktop.md +0 -0
  67. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/33-webhooks-files.md +0 -0
  68. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/docs/34-ui-and-motion.md +0 -0
  69. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/__init__.py +0 -0
  70. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/_speedups.py +0 -0
  71. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/_templates.py +0 -0
  72. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/a11y.py +0 -0
  73. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ai/guide.md +0 -0
  74. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/app.py +0 -0
  75. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/asgi.py +0 -0
  76. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/auth.py +0 -0
  77. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/__init__.py +0 -0
  78. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/admin.py +0 -0
  79. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/html.py +0 -0
  80. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/models.py +0 -0
  81. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/oauth.py +0 -0
  82. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/orgpages.py +0 -0
  83. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/orgs.py +0 -0
  84. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/pages.py +0 -0
  85. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/passwords.py +0 -0
  86. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/privacy.py +0 -0
  87. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/teams.py +0 -0
  88. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/throttle.py +0 -0
  89. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/tokens.py +0 -0
  90. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/authkit/webauthn.py +0 -0
  91. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/browser.py +0 -0
  92. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/build.py +0 -0
  93. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/cache.py +0 -0
  94. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/cli/__main__.py +0 -0
  95. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/__init__.py +0 -0
  96. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/ast.py +0 -0
  97. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/codegen/__init__.py +0 -0
  98. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/codegen/ir.py +0 -0
  99. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/parser.py +0 -0
  100. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/compiler/rpc.py +0 -0
  101. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/config.py +0 -0
  102. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/context.py +0 -0
  103. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/csp_report.py +0 -0
  104. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/css.py +0 -0
  105. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/db/__init__.py +0 -0
  106. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/db/dialect.py +0 -0
  107. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/db/migrate.py +0 -0
  108. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/db/rls.py +0 -0
  109. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/db/schema.py +0 -0
  110. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/db/seeds.py +0 -0
  111. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/decorators.py +0 -0
  112. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/deploy/__init__.py +0 -0
  113. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/deploy/legacy.py +0 -0
  114. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/deploy/plan.py +0 -0
  115. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/deploy/targets.py +0 -0
  116. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/deprecation.py +0 -0
  117. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/desktop/__init__.py +0 -0
  118. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/desktop/gate.py +0 -0
  119. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/desktop/package.py +0 -0
  120. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/desktop/shell.py +0 -0
  121. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/devlink.py +0 -0
  122. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/dts.py +0 -0
  123. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/fieldcrypt.py +0 -0
  124. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/filegc.py +0 -0
  125. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/forms.py +0 -0
  126. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/hosting.py +0 -0
  127. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/http.py +0 -0
  128. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/__init__.py +0 -0
  129. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/builtin.py +0 -0
  130. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/core.py +0 -0
  131. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/db.py +0 -0
  132. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/legacy.py +0 -0
  133. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/memory.py +0 -0
  134. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/redis.py +0 -0
  135. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/schedule.py +0 -0
  136. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/jobs/worker.py +0 -0
  137. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/keys.py +0 -0
  138. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/livedata.py +0 -0
  139. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/livetable.py +0 -0
  140. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/lsp.py +0 -0
  141. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/lsp_data.py +0 -0
  142. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/lsp_teams.py +0 -0
  143. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/mail.py +0 -0
  144. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/markdown.py +0 -0
  145. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/mcp_http.py +0 -0
  146. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/models/__init__.py +0 -0
  147. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/models/fields.py +0 -0
  148. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/models/query.py +0 -0
  149. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/models/relations.py +0 -0
  150. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/motion.py +0 -0
  151. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/net/__init__.py +0 -0
  152. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/net/http.py +0 -0
  153. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/net/live.py +0 -0
  154. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/net/server.py +0 -0
  155. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/net/ws.py +0 -0
  156. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/observability.py +0 -0
  157. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/openapi.py +0 -0
  158. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/packages.py +0 -0
  159. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/platform.py +0 -0
  160. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/plugins.py +0 -0
  161. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/profile.py +0 -0
  162. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/push.py +0 -0
  163. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/pwa.py +0 -0
  164. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/py.typed +0 -0
  165. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/realtime.py +0 -0
  166. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/rooms.py +0 -0
  167. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/rpc.py +0 -0
  168. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/rules.py +0 -0
  169. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/auth.js +0 -0
  170. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/desktop.js +0 -0
  171. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/forms.js +0 -0
  172. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/live.js +0 -0
  173. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/markdown.js +0 -0
  174. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/motion-api.js +0 -0
  175. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/motion-count.js +0 -0
  176. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/motion-drag.js +0 -0
  177. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/motion-layout.js +0 -0
  178. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/motion-math.js +0 -0
  179. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/motion.js +0 -0
  180. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/pwa.js +0 -0
  181. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/sw.js +0 -0
  182. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/accordion.js +0 -0
  183. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/calendar.js +0 -0
  184. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/carousel.js +0 -0
  185. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/checkbox.js +0 -0
  186. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/collapsible.js +0 -0
  187. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/combobox.js +0 -0
  188. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/command.js +0 -0
  189. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/context-menu.js +0 -0
  190. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/core.js +0 -0
  191. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/dialog.js +0 -0
  192. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/hover-card.js +0 -0
  193. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/index.js +0 -0
  194. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/listbox.js +0 -0
  195. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/menu.js +0 -0
  196. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/menubar.js +0 -0
  197. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/navigation-menu.js +0 -0
  198. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/otp.js +0 -0
  199. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/popover.js +0 -0
  200. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/radio-group.js +0 -0
  201. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/resizable.js +0 -0
  202. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/select.js +0 -0
  203. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/sidebar.js +0 -0
  204. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/slider.js +0 -0
  205. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/tabs.js +0 -0
  206. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/toaster.js +0 -0
  207. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/toggle-group.js +0 -0
  208. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/toggle.js +0 -0
  209. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/browser/ui/tooltip.js +0 -0
  210. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/runtime/server/argcheck.py +0 -0
  211. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/sbom.py +0 -0
  212. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/secret_scan.py +0 -0
  213. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/security.py +0 -0
  214. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/serve.py +0 -0
  215. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ssr.py +0 -0
  216. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ssr_codegen.py +0 -0
  217. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/storage.py +0 -0
  218. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/sync.py +0 -0
  219. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/telemetry/__init__.py +0 -0
  220. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/telemetry/devtools.py +0 -0
  221. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/telemetry/instruments.py +0 -0
  222. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/telemetry/logs.py +0 -0
  223. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/telemetry/metrics.py +0 -0
  224. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/telemetry/tracing.py +0 -0
  225. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/ai-chat.pyweb +0 -0
  226. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/app.css +0 -0
  227. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/auth.pyweb +0 -0
  228. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/blog.pyweb +0 -0
  229. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/chat.pyweb +0 -0
  230. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/counter.pyweb +0 -0
  231. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/desktop/app.pyweb +0 -0
  232. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/desktop/test_app.py +0 -0
  233. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/saas/app.pyweb +0 -0
  234. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/saas/seeds.py +0 -0
  235. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/saas/test_app.py +0 -0
  236. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/templates/todo.pyweb +0 -0
  237. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/tenancy.py +0 -0
  238. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/testing.py +0 -0
  239. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/__init__.py +0 -0
  240. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/cli.py +0 -0
  241. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/button.pyweb +0 -0
  242. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/card.pyweb +0 -0
  243. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/carousel.pyweb +0 -0
  244. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/chart.pyweb +0 -0
  245. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/command.pyweb +0 -0
  246. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/controls.pyweb +0 -0
  247. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/data.pyweb +0 -0
  248. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/date.pyweb +0 -0
  249. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/dialog.pyweb +0 -0
  250. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/disclosure.pyweb +0 -0
  251. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/display.pyweb +0 -0
  252. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/layout.pyweb +0 -0
  253. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/menu.pyweb +0 -0
  254. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/overlay.pyweb +0 -0
  255. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/kit/style.pyweb +0 -0
  256. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/serve.py +0 -0
  257. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/source_edit.py +0 -0
  258. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/static/ui.css +0 -0
  259. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/ui/theme.py +0 -0
  260. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/uploads.py +0 -0
  261. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb/webhooks.py +0 -0
  262. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/dependency_links.txt +0 -0
  263. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/entry_points.txt +0 -0
  264. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/requires.txt +0 -0
  265. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/pyweb_stack.egg-info/top_level.txt +0 -0
  266. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/setup.cfg +0 -0
  267. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/setup.py +0 -0
  268. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_account_privacy.py +0 -0
  269. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_api_tokens.py +0 -0
  270. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_auth_contract.py +0 -0
  271. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_auth_security.py +0 -0
  272. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_auth_sessions.py +0 -0
  273. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_auth_v1.py +0 -0
  274. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_authkit.py +0 -0
  275. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_authkit_units.py +0 -0
  276. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_backplane.py +0 -0
  277. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_browser_build.py +0 -0
  278. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_budget.py +0 -0
  279. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_cli_deploy.py +0 -0
  280. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_codegen.py +0 -0
  281. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_compiler.py +0 -0
  282. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_cron.py +0 -0
  283. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_customise.py +0 -0
  284. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_data_layer.py +0 -0
  285. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_db_production.py +0 -0
  286. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_deploy_upload_render.py +0 -0
  287. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_deploy_v5.py +0 -0
  288. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_desktop.py +0 -0
  289. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_docs.py +0 -0
  290. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_dts.py +0 -0
  291. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_dx_v5.py +0 -0
  292. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_e2e.py +0 -0
  293. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_encrypted_fields.py +0 -0
  294. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_examples.py +0 -0
  295. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_filegc.py +0 -0
  296. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_forms_security_obs.py +0 -0
  297. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_fuzz_requests.py +0 -0
  298. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_http_fetch.py +0 -0
  299. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_jobs_app.py +0 -0
  300. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_jobs_cache_realtime.py +0 -0
  301. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_jobs_v5.py +0 -0
  302. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_keys_sessions.py +0 -0
  303. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_live_patches.py +0 -0
  304. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_livedata.py +0 -0
  305. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_lsp.py +0 -0
  306. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_lsp_teams.py +0 -0
  307. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_markdown.py +0 -0
  308. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_mcp_teams.py +0 -0
  309. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_mcp_v6.py +0 -0
  310. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_migrations_property.py +0 -0
  311. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_migrations_v5.py +0 -0
  312. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_models_db.py +0 -0
  313. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_motion.py +0 -0
  314. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_multifile.py +0 -0
  315. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_multipage.py +0 -0
  316. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_net.py +0 -0
  317. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_observability.py +0 -0
  318. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_openapi.py +0 -0
  319. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_org_pages.py +0 -0
  320. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_packages.py +0 -0
  321. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_parser.py +0 -0
  322. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_parser_v1.py +0 -0
  323. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_plugins_platform_bench.py +0 -0
  324. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_production_gaps.py +0 -0
  325. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_profile.py +0 -0
  326. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_push.py +0 -0
  327. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_pwa.py +0 -0
  328. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_pyjs_semantics.py +0 -0
  329. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_rate_per_function.py +0 -0
  330. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_reactivity.py +0 -0
  331. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_realtime_transport.py +0 -0
  332. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_reference_apps.py +0 -0
  333. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_request_hardening.py +0 -0
  334. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_rpc_placement.py +0 -0
  335. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_rpc_production.py +0 -0
  336. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_runtime_signals.py +0 -0
  337. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_sbom.py +0 -0
  338. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_secret_scan.py +0 -0
  339. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_server_db.py +0 -0
  340. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_speedups.py +0 -0
  341. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_ssr_codegen.py +0 -0
  342. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_static_hashing.py +0 -0
  343. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_static_serving.py +0 -0
  344. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_streaming.py +0 -0
  345. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_sync_live.py +0 -0
  346. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_systems.py +0 -0
  347. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_telemetry.py +0 -0
  348. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_tenancy.py +0 -0
  349. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_testing_orgs.py +0 -0
  350. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_threat_model.py +0 -0
  351. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_trusted_types.py +0 -0
  352. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_ui_kit.py +0 -0
  353. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_webhooks.py +0 -0
  354. {pyweb_stack-0.6.0 → pyweb_stack-0.6.1}/tests/test_website.py +0 -0
@@ -6,6 +6,59 @@ project uses [semantic versioning](https://semver.org).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.6.1]
10
+
11
+ Better error messages, `pyweb doctor`, a `pyweb dev` that keeps your place
12
+ when it reloads, much faster reloads and tests, and fixes found by
13
+ installing 0.6.0 from PyPI and putting every template through every
14
+ command. Nothing to change in your apps.
15
+
16
+ ### Added
17
+ - **Better error messages.** Compile errors show the line with a caret under
18
+ the problem, suggest the name you meant (a prop, component, variable,
19
+ server function argument, kit variant or motion preset: "did you mean
20
+ 'name'?"), and link the docs page that explains them. The CLI, the
21
+ `pyweb dev` overlay and the MCP tools (`frame`, `docs` fields) show the
22
+ same. When page code raises in `pyweb dev`, the error page starts with the
23
+ frames in your own files and their source lines.
24
+ - **`pyweb dev` keeps your place when it reloads.** After a save, the page
25
+ comes back with its variables (a counter's count, a bound input's text),
26
+ unbound form fields, scroll position and focus as they were. A variable
27
+ whose type changed, or that was renamed, starts over; add `?fresh` to the
28
+ URL to start everything over. Production builds only grow by the names
29
+ of page variables.
30
+ - **`pyweb doctor`** checks the project and the machine in one go: Python,
31
+ whether the app compiles, optional packages the app needs, the database
32
+ and pending migrations, Redis, secrets written in the source, changes
33
+ `pyweb upgrade` wants, a stale dev server link, tests, the port and the
34
+ editor extension, each with a fix (`--production` adds the production
35
+ settings, `--json` for CI). Assistants get it as the `pyweb_doctor` tool.
36
+ - **Faster reloads and tests.** The UI kit is compiled once per process and
37
+ each file's Python code is reused until it changes: on the saas example a
38
+ `pyweb dev` reload after a save went from about 255 to 60 ms, a
39
+ `TestClient` from about 255 to 55 ms, and compiling from about 240 to
40
+ 60 ms. The dev server notices a save within 0.1 s (was 0.4 s) and the
41
+ page asks for the new version every 0.25 s (was 0.6 s).
42
+ `python -m pyweb.bench --system` reports `reload_ms` and `test_client_ms`,
43
+ and CI's benchmark gate watches them.
44
+ - `pyweb check` warns about kit props and motion values that won't work
45
+ (`<Button variant="outlin">`), as the editor already did.
46
+
47
+ ### Fixed
48
+ - `pyweb check` printed the file and line twice for compile errors.
49
+ - Components drawn as a kit button (`DialogClose`) were flagged by the
50
+ editor for `variant="default"`, which they support.
51
+ - `pyweb test` with no path ran `pytest tests/`, which new apps don't have
52
+ (their test is `test_app.py` next to `app.pyweb`), so it failed in every
53
+ new project. It now runs the current folder, and accepts the app's
54
+ `.pyweb` file as well as a test file or folder.
55
+ - The `pyweb_test` MCP tool given the app's `.pyweb` file (as every other
56
+ tool takes) found no tests; it now tests that file's folder.
57
+ - `pyweb upgrade` says to apply pending migrations before creating the
58
+ `auth_sessions` one (on a fresh checkout `pyweb db diff` refused to run),
59
+ and notes that 0.6's `pyweb check` reports accessibility problems, which
60
+ `--strict` treats as failures.
61
+
9
62
  ## [0.6.0]
10
63
 
11
64
  Teams, installable and desktop apps, a UI kit and motion, and security by
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyweb-stack
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: Full-stack web apps in one Python file: server-rendered pages, reactive browser UI, typed RPC.
5
5
  Author-email: MaanavKrishna <67054795+MaanavKrishna@users.noreply.github.com>
6
6
  Maintainer-email: MaanavKrishna <67054795+MaanavKrishna@users.noreply.github.com>
@@ -478,7 +478,7 @@ The [architecture overview](ARCHITECTURE.md) goes through each stage.
478
478
 
479
479
  ## Build it with AI
480
480
 
481
- PyWeb ships an MCP server with 29 tools, so AI assistants can do what
481
+ PyWeb ships an MCP server with 30 tools, so AI assistants can do what
482
482
  you'd do: scaffold an app, search the docs, check each edit (errors come
483
483
  back as line numbers with fix hints), build with the UI kit and a theme,
484
484
  render pages and call server functions as any signed-in member, look at
@@ -566,7 +566,7 @@ Upgrade with `pip install -U pyweb-stack`.
566
566
 
567
567
  | Version | Highlights |
568
568
  |---|---|
569
- | 0.6 | Teams with roles, invitations and tenant-scoped Models (Postgres row-level security), API tokens and OpenAPI, webhooks, installable apps with Web Push, desktop windows and installers, a UI kit with themes and motion, Trusted Types, encrypted fields and a threat model, about 2.5× faster serving, Python 3.15 and free-threaded builds, a much richer MCP server |
569
+ | 0.6 | Teams with roles, invitations and tenant-scoped Models (Postgres row-level security), API tokens and OpenAPI, webhooks, installable apps with Web Push, desktop windows and installers, a UI kit with themes and motion, Trusted Types, encrypted fields and a threat model, about 2.5× faster serving, Python 3.15 and free-threaded builds, a much richer MCP server; 0.6.1: error messages with code frames and suggestions, `pyweb doctor`, dev reloads that keep page state, 4× faster reloads and tests |
570
570
  | 0.5 | Models with migrations, forms, auth kit with passkeys, OAuth and an admin, row-level live updates over WebSockets on PyWeb's own server, durable jobs and schedules, `pyweb deploy` for docker, compose, k8s, fly, render and railway, logs, metrics, tracing and a dev toolbar, data-aware editor and MCP tools, the `saas` template, `pyweb upgrade` |
571
571
  | 0.4 | npm packages without Node, layouts and client-side navigation, streaming server functions and `<Markdown>` for AI apps, live queries, page head tags, `.pyweb` error pages; 0.4.1: richer MCP tools; 0.4.2: VS Code run, new-app and MCP commands; 0.4.3: fixes, security hardening, gzip and caching; 0.4.4: key rotation, session revocation, typed arguments, Redis-shared limits, `check --production` |
572
572
  | 0.3 | Hydration, live updates (SSE), multi-file apps, language server + VS Code extension, browser playground, faster rendering, screenshot/test MCP tools |
@@ -404,7 +404,7 @@ The [architecture overview](ARCHITECTURE.md) goes through each stage.
404
404
 
405
405
  ## Build it with AI
406
406
 
407
- PyWeb ships an MCP server with 29 tools, so AI assistants can do what
407
+ PyWeb ships an MCP server with 30 tools, so AI assistants can do what
408
408
  you'd do: scaffold an app, search the docs, check each edit (errors come
409
409
  back as line numbers with fix hints), build with the UI kit and a theme,
410
410
  render pages and call server functions as any signed-in member, look at
@@ -492,7 +492,7 @@ Upgrade with `pip install -U pyweb-stack`.
492
492
 
493
493
  | Version | Highlights |
494
494
  |---|---|
495
- | 0.6 | Teams with roles, invitations and tenant-scoped Models (Postgres row-level security), API tokens and OpenAPI, webhooks, installable apps with Web Push, desktop windows and installers, a UI kit with themes and motion, Trusted Types, encrypted fields and a threat model, about 2.5× faster serving, Python 3.15 and free-threaded builds, a much richer MCP server |
495
+ | 0.6 | Teams with roles, invitations and tenant-scoped Models (Postgres row-level security), API tokens and OpenAPI, webhooks, installable apps with Web Push, desktop windows and installers, a UI kit with themes and motion, Trusted Types, encrypted fields and a threat model, about 2.5× faster serving, Python 3.15 and free-threaded builds, a much richer MCP server; 0.6.1: error messages with code frames and suggestions, `pyweb doctor`, dev reloads that keep page state, 4× faster reloads and tests |
496
496
  | 0.5 | Models with migrations, forms, auth kit with passkeys, OAuth and an admin, row-level live updates over WebSockets on PyWeb's own server, durable jobs and schedules, `pyweb deploy` for docker, compose, k8s, fly, render and railway, logs, metrics, tracing and a dev toolbar, data-aware editor and MCP tools, the `saas` template, `pyweb upgrade` |
497
497
  | 0.4 | npm packages without Node, layouts and client-side navigation, streaming server functions and `<Markdown>` for AI apps, live queries, page head tags, `.pyweb` error pages; 0.4.1: richer MCP tools; 0.4.2: VS Code run, new-app and MCP commands; 0.4.3: fixes, security hardening, gzip and caching; 0.4.4: key rotation, session revocation, typed arguments, Redis-shared limits, `check --production` |
498
498
  | 0.3 | Hydration, live updates (SSE), multi-file apps, language server + VS Code extension, browser playground, faster rendering, screenshot/test MCP tools |
@@ -88,6 +88,12 @@ pyweb serve dist # production server with /healthz
88
88
  `dist/` contains your app source, static assets and a `Dockerfile`; see
89
89
  [Deployment](12-deployment.md).
90
90
 
91
+ If something doesn't work and the error isn't clear, `pyweb doctor`
92
+ checks the project and your machine (Python, the app, optional packages,
93
+ the database and migrations, the port, your editor) and prints a fix for
94
+ each problem; `pyweb doctor --production` also checks the production
95
+ settings.
96
+
91
97
  ## Next
92
98
 
93
99
  The [tutorial](03-tutorial.md) builds a notes app with a database,
@@ -5,13 +5,20 @@ pyweb new NAME [--template T] create NAME/ with app.pyweb, test_app.py, A
5
5
  T = blank | counter (default) | todo | blog | saas | auth | chat | ai-chat | desktop
6
6
  (apps with a database also get migrations/0001_initial.py and a README)
7
7
  pyweb dev FILE [--port 8000] [--host 127.0.0.1] [--no-reload]
8
- development server: recompile on save, live reload, error overlay,
8
+ development server: recompile on save, live reload that keeps the
9
+ page's variables, form fields, scroll and focus (add ?fresh to
10
+ the URL to start over), error overlay,
9
11
  the dev toolbar (requests, SQL, jobs, emails, theme and motion
10
12
  editors), and background jobs in the same process; writes
11
13
  .pyweb/dev.json so `pyweb mcp` can see its errors and logs
12
14
  pyweb check FILE [--production] [--strict]
13
15
  compile, security and accessibility checks; --production also checks
14
16
  the environment; --strict fails on warnings too
17
+ pyweb doctor [FILE] [--production] [--json] [--port 8000]
18
+ is the project ready? Python, the app compiles, optional packages,
19
+ database and migrations, Redis, secrets in the source, upgrade
20
+ changes, a stale dev server link, tests, the port, the editor; a fix
21
+ for each problem (exit 1 on any failure; --json for CI)
15
22
  pyweb upgrade [FILE] [--fix] what to change for this PyWeb version (file and line); --fix applies
16
23
  the safe rewrites
17
24
  pyweb inspect FILE [--security] where every name runs and why; RPC table; findings
@@ -66,13 +73,39 @@ pyweb dts FILE.d.ts Python dataclasses from TypeScript declarat
66
73
  pyweb mcp MCP server over stdio for AI assistants (see AI assistants & MCP)
67
74
  pyweb mcp --http [--port 8765] [--token T] the same over Streamable HTTP (127.0.0.1 only without a token)
68
75
  pyweb lsp language server over stdio for editors (see below)
69
- pyweb test [PATH] run pytest
76
+ pyweb test [PATH] run pytest on PATH (a test file, a folder or app.pyweb's folder;
77
+ default: the current folder, where new apps keep test_app.py)
70
78
  pyweb fmt [PATH] / pyweb lint [PATH] run ruff format / ruff check (if installed)
71
79
  pyweb --version
72
80
  ```
73
81
 
74
82
  `python -m pyweb.cli ...` is equivalent to `pyweb ...`.
75
83
 
84
+ ## Error messages
85
+
86
+ A compile error names the file and line, shows the line with a caret under
87
+ the problem, suggests the name you probably meant, and links the docs page
88
+ that explains it:
89
+
90
+ ```text
91
+ error: app.pyweb:17: <Greeting> has no prop 'nme'; did you mean 'name'?
92
+
93
+ 16 | <h1>{count}</h1>
94
+ > 17 | <Greeting nme="x" />
95
+ | ^
96
+ 18 | </main>
97
+
98
+ hint: Use one of the component's parameter names as the attribute.
99
+ docs: https://maanavkrishna.github.io/PyWeb/language.html
100
+ ```
101
+
102
+ The first line keeps the `file:line: message` shape, so editors and CI
103
+ logs can jump to it. `pyweb check` also warns about kit props and motion
104
+ values that won't work (`<Button variant="outlin">`). The `pyweb dev`
105
+ overlay shows the same, and when page code raises, its error page starts
106
+ with the frames in your own files and their source lines, before the full
107
+ traceback.
108
+
76
109
  ## Exit codes
77
110
 
78
111
  `check` and `build` exit `1` on compile or security errors and `2` on
@@ -11,8 +11,9 @@ can decide up front whether they matter for your app.
11
11
  - **npm packages must ship ES modules.** CommonJS-only packages and
12
12
  packages built on Node.js modules can't run in the browser; see
13
13
  [npm packages](18-npm-packages.md).
14
- - **Dev reload is a full page reload.** State is not preserved across
15
- edits.
14
+ - **Dev reload keeps page variables, not component state.** After a save,
15
+ `pyweb dev` reloads the page with its variables, form fields, scroll
16
+ and focus as they were; state inside components starts over.
16
17
  - **SQLite is for one machine.** Several processes on one host share it
17
18
  fine, but more than one server needs Postgres or MySQL;
18
19
  `pyweb check --production` and `pyweb deploy` say so.
@@ -33,7 +34,9 @@ can decide up front whether they matter for your app.
33
34
  with Web Push, desktop windows and installers, a UI kit with themes and
34
35
  motion, Trusted Types, encrypted fields and a threat model, about 2.5×
35
36
  faster serving, Python 3.15 and free-threaded builds, and a much richer
36
- MCP server.
37
+ MCP server. 0.6.1 added error messages with code frames and
38
+ suggestions, `pyweb doctor`, dev reloads that keep page state, and 4×
39
+ faster reloads and tests.
37
40
  - **0.5**: production apps. Models with relations and migrations, forms
38
41
  checked in the browser and on the server, an auth kit (passwords,
39
42
  passkeys, emailed links, OAuth, two-step sign-in, an admin), row-level
@@ -64,7 +67,8 @@ Planned, roughly in order. Nothing here is promised for a date.
64
67
  1. **1.0**: the API, RPC protocol and CLI frozen under semantic versioning,
65
68
  after 0.6 has been used in production for a while.
66
69
  2. **File uploads from the playground and the admin.**
67
- 3. **State-preserving hot reload** while developing.
70
+ 3. **Hot reload without a page reload** while developing (0.6.1 already
71
+ keeps the page's variables, fields, scroll and focus across reloads).
68
72
 
69
73
  Ideas, bug reports and pull requests are welcome; see
70
74
  [CONTRIBUTING.md](https://github.com/MaanavKrishna/PyWeb/blob/main/CONTRIBUTING.md).
@@ -70,7 +70,7 @@ tool runs. Slow tools (tests, screenshots, profiling) report progress.
70
70
  | `pyweb_guide` | The rules for writing `.pyweb` apps (or one section): what runs where, state, markup, components, server functions, the browser Python subset, and every common error with its fix. |
71
71
  | `pyweb_docs` | Searches the documentation (every page, the guide and the threat model) and returns the matching sections, or one whole page. For any "how do I..." question. |
72
72
  | `pyweb_new_app` | Creates an app from a template (`blank`, `counter`, `todo`, `blog`, `saas`, `auth`, `chat`, `ai-chat`, `desktop`) with `AGENTS.md`, `CLAUDE.md`, tests and a stylesheet; apps with a database also get their first migration. `saas` (teams, accounts, tenant Models, forms, live pages, jobs, webhooks, admin) is the best start for a product; `desktop` is a notes app for `pyweb desktop`. |
73
- | `pyweb_check` | Compiles the app and runs the security and accessibility checks. Returns `ok`, errors with `line`, `message` and a fix `hint`, plus pages (with their layouts, live-data variables and npm packages), signals, what's sent to the browser, JS size and the server functions. |
73
+ | `pyweb_check` | Compiles the app and runs the security and accessibility checks. Returns `ok`, errors with `line`, `message`, a fix `hint`, a code `frame` and a `docs` link, plus pages (with their layouts, live-data variables and npm packages), signals, what's sent to the browser, JS size and the server functions. |
74
74
  | `pyweb_explain` | What your editor shows for a line: for each name, where it runs and why (or its signature or npm type), the problems on that line and, with `complete`, the completions at a column. |
75
75
  | `pyweb_inspect` | For every name: does it run in the browser or on the server, and why. |
76
76
  | `pyweb_compiled` | The JavaScript generated for a page, and its server-rendered HTML. |
@@ -106,6 +106,7 @@ tool runs. Slow tools (tests, screenshots, profiling) report progress.
106
106
  | Tool | What it does |
107
107
  |---|---|
108
108
  | `pyweb_dev` | Whether `pyweb dev` is running for this app: its URL, whether the app compiles right now (with the error and traceback), its pages and recent 500 errors. |
109
+ | `pyweb_doctor` | Project health, as `pyweb doctor`: each check with `status` (ok, warn, fail), `detail` and a `fix`; `production=true` adds the production settings. |
109
110
  | `pyweb_logs` | The dev server's log lines (warnings, errors with tracebacks, slow queries, job failures); `since` returns only newer ones. |
110
111
 
111
112
  When `pyweb dev` starts, it writes `.pyweb/dev.json` next to the app: its
@@ -19,6 +19,7 @@ pytest
19
19
  Apps that call `app.use_auth()` need the new `auth_sessions` table:
20
20
 
21
21
  ```bash
22
+ pyweb db upgrade # apply any migrations still pending
22
23
  pyweb db diff --name auth_sessions
23
24
  pyweb db upgrade
24
25
  ```
@@ -107,12 +107,22 @@ you use it in a browser.
107
107
  Profile against a copy of your real data. A page that's fast with ten
108
108
  rows can be slow with ten thousand.
109
109
 
110
+ ## Fast reloads and tests
111
+
112
+ The UI kit is compiled once per process, and each `.pyweb` file's Python
113
+ code is reused until the file changes. On the saas example that took a
114
+ reload in `pyweb dev` from about 255 to 60 ms and creating a
115
+ `TestClient` from about 255 to 55 ms; the dev server also notices a save
116
+ within a tenth of a second. Compiled output is byte-for-byte what a fresh
117
+ compile gives, and a test checks that.
118
+
110
119
  ## Benchmarks in CI
111
120
 
112
121
  `python -m pyweb.bench` reports compile time, shipped JavaScript and
113
122
  render time per page. `--system` measures the whole stack: requests per
114
123
  second over real sockets, a signed-in page with a database, start-up
115
- time, and live query hashing.
124
+ time, live query hashing, and how long loading an app again takes (what
125
+ `pyweb dev` does after a save, and what each `TestClient` does).
116
126
 
117
127
  PyWeb's CI runs `--compare` on every push and pull request. It checks out
118
128
  the commit before the change next to the new one and runs both five
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyweb-stack"
7
- version = "0.6.0"
7
+ version = "0.6.1"
8
8
  description = "Full-stack web apps in one Python file: server-rendered pages, reactive browser UI, typed RPC."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -66,7 +66,7 @@ pyweb = "pyweb.cli:main"
66
66
  include = ["pyweb*"]
67
67
 
68
68
  [tool.setuptools.package-data]
69
- pyweb = ["runtime/browser/*.js", "runtime/browser/ui/*.js", "ui/kit/*.pyweb", "ui/static/*", "py.typed", "ai/*.md", "templates/*.pyweb", "templates/*.css", "templates/*/*.pyweb", "templates/*/*.py", "templates/*/*.md"]
69
+ pyweb = ["runtime/browser/*.js", "runtime/browser/ui/*.js", "runtime/browser/dev/*.js", "ui/kit/*.pyweb", "ui/static/*", "py.typed", "ai/*.md", "templates/*.pyweb", "templates/*.css", "templates/*/*.pyweb", "templates/*/*.py", "templates/*/*.md"]
70
70
 
71
71
  [tool.pytest.ini_options]
72
72
  testpaths = ["tests"]
@@ -30,10 +30,10 @@ def _state_function(fn):
30
30
  Markup placeholders are dropped; an explicit ``return`` (e.g.
31
31
  ``return redirect("/login")``) still returns early.
32
32
  """
33
- new = copy.deepcopy(fn)
33
+ new = copy.copy(fn) # only the kept statements need copies, not the markup
34
34
  new.name = STATE_PREFIX + fn.name
35
35
  new.decorator_list = []
36
- body = [s for s in new.body if not is_ui_stmt(s)]
36
+ body = [copy.deepcopy(s) for s in fn.body if not is_ui_stmt(s)]
37
37
  ret = ast.Return(value=ast.Call(func=ast.Name(id="locals", ctx=ast.Load()), args=[], keywords=[]))
38
38
  body.append(ret)
39
39
  new.body = body
@@ -41,6 +41,28 @@ def _state_function(fn):
41
41
  return new
42
42
 
43
43
 
44
+ _CODE: dict = {} # (file, source hash, page/component names) -> code object
45
+
46
+
47
+ def _module_code(source, filename, infos):
48
+ """The Python code for a ``.pyweb`` module: its Python part plus a state function per page and
49
+ component. Cached by source, so reloading an app (or loading it again in tests) skips re-parsing
50
+ the files that didn't change, the UI kit's above all."""
51
+ import hashlib
52
+ key = (filename, hashlib.sha256(source.encode()).hexdigest(), tuple(getattr(i, "name", "") for i in infos))
53
+ code = _CODE.get(key)
54
+ if code is None:
55
+ py_source, _ui = P.split_sources(source)
56
+ tree = ast.parse(py_source, filename=filename)
57
+ for info in infos:
58
+ tree.body.append(_state_function(info.node))
59
+ code = compile(tree, filename, "exec")
60
+ if len(_CODE) > 256:
61
+ _CODE.clear()
62
+ _CODE[key] = code
63
+ return code
64
+
65
+
44
66
  def _kit_markup(path):
45
67
  """Whether a component file is kit markup (a component copied with ``pyweb ui add``)."""
46
68
  try:
@@ -169,10 +191,7 @@ class LoadedApp:
169
191
 
170
192
  # ----------------------------------------------------------- module
171
193
  def _exec(self, source, filename, infos, module_name=None):
172
- py_source, _ui = P.split_sources(source)
173
- tree = ast.parse(py_source, filename=filename)
174
- for info in infos:
175
- tree.body.append(_state_function(info.node))
194
+ code = _module_code(source, filename, infos)
176
195
  stem = os.path.splitext(os.path.basename(filename))[0] or "app"
177
196
  mod = types.ModuleType(module_name or f"pyweb_app_{stem}")
178
197
  mod.__file__ = os.path.abspath(filename) if self.path else filename
@@ -191,7 +210,7 @@ class LoadedApp:
191
210
  if app_dir and app_dir not in sys.path:
192
211
  sys.path.insert(0, app_dir)
193
212
  sys.modules[mod.__name__] = mod
194
- exec(compile(tree, filename, "exec"), mod.__dict__) # noqa: S102 - the app itself
213
+ exec(code, mod.__dict__) # noqa: S102 - the app itself
195
214
  return mod
196
215
 
197
216
  # -------------------------------------------------------- rendering
@@ -112,6 +112,8 @@ SYSTEM = {
112
112
  "import_ms": (False, 5.0),
113
113
  "cli_version_ms": (False, 8.0),
114
114
  "live_10k_ms": (False, 2.0),
115
+ "reload_ms": (False, 15.0),
116
+ "test_client_ms": (False, 15.0),
115
117
  }
116
118
 
117
119
 
@@ -225,6 +227,38 @@ def startup_ms(rounds=7):
225
227
  round(max(0.0, wall(["-m", "pyweb.cli", "--version"]) - floor), 1))
226
228
 
227
229
 
230
+ def reload_ms(rounds=5):
231
+ """Loading the saas app again in a warm process (what ``pyweb dev`` does after a save) and creating a
232
+ ``TestClient`` for it, in milliseconds."""
233
+ import os
234
+ import shutil
235
+ import tempfile
236
+ from pyweb.hosting import Site
237
+ from pyweb.testing import TestClient
238
+ old = os.getcwd()
239
+ fixed = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "bench", "apps", "saas")
240
+ with tempfile.TemporaryDirectory() as tmp:
241
+ try:
242
+ if os.path.isdir(fixed):
243
+ shutil.copytree(fixed, os.path.join(tmp, "team"))
244
+ else:
245
+ from pyweb.mcp import scaffold
246
+ scaffold(os.path.join(tmp, "team"), template="saas")
247
+ os.chdir(os.path.join(tmp, "team"))
248
+ Site("app.pyweb") # the first load fills the caches
249
+ reloads, clients = [], []
250
+ for _ in range(rounds):
251
+ t0 = time.perf_counter()
252
+ Site("app.pyweb")
253
+ reloads.append((time.perf_counter() - t0) * 1000)
254
+ t0 = time.perf_counter()
255
+ TestClient("app.pyweb")
256
+ clients.append((time.perf_counter() - t0) * 1000)
257
+ return round(_median(reloads), 1), round(_median(clients), 1)
258
+ finally:
259
+ os.chdir(old)
260
+
261
+
228
262
  def live_10k_ms():
229
263
  """Version hash plus row diff for a 10,000-row live query, in milliseconds."""
230
264
  from pyweb.livedata import _version, diff
@@ -244,8 +278,9 @@ def system():
244
278
  import os
245
279
  os.environ.setdefault("PYWEB_WORKER", "0")
246
280
  imp, cli = startup_ms()
281
+ reload, client = reload_ms()
247
282
  return {"network_rps": network_rps(), "saas_page_us": saas_page_us(), "import_ms": imp,
248
- "cli_version_ms": cli, "live_10k_ms": live_10k_ms()}
283
+ "cli_version_ms": cli, "live_10k_ms": live_10k_ms(), "reload_ms": reload, "test_client_ms": client}
249
284
 
250
285
 
251
286
  def regressions(base, head, *, tolerance=0.15):
@@ -14,6 +14,17 @@ def _load(path):
14
14
  return fh.read()
15
15
 
16
16
 
17
+ def _pretty_error(exc, path=None):
18
+ """A compile error as terminal text: where, the code frame, a hint and a docs link."""
19
+ from pyweb.compiler.errors import pretty
20
+ path = getattr(exc, "filename", None) or path
21
+ try:
22
+ source = _load(path) if path else None
23
+ except OSError:
24
+ source = None
25
+ return pretty(exc, source, path)
26
+
27
+
17
28
  def cmd_inspect(args):
18
29
  from pyweb.compiler import compile_source
19
30
  src = _load(args.file)
@@ -41,6 +52,12 @@ def cmd_inspect(args):
41
52
  print(f"{finding['kind']} {args.file}:{finding['line']}: {finding['message']}")
42
53
 
43
54
 
55
+ def cmd_doctor(args):
56
+ """Is this project and this machine ready? One line per check, with a fix for each problem."""
57
+ from pyweb import doctor
58
+ raise SystemExit(doctor.main(args.file, production=args.production, as_json=args.json, port=args.port))
59
+
60
+
44
61
  def cmd_check(args):
45
62
  from pyweb.compiler import compile_source
46
63
  from pyweb.security import check_source
@@ -48,17 +65,22 @@ def cmd_check(args):
48
65
  src = _load(args.file)
49
66
  findings = check_source(src, args.file)
50
67
  findings += scan_app(args.file)
51
- from pyweb import a11y
52
- findings += a11y.check(src, args.file)
68
+ from pyweb import a11y, lsp_ui
69
+ findings += a11y.check(src, args.file) + lsp_ui.check(src, args.file)
53
70
  try:
54
71
  out = compile_source(src, filename=args.file)
55
72
  n_sig = sum(len(p["signals"]) for p in out["pages"].values())
56
73
  print(f"ok: {len(out['pages'])} page(s), {n_sig} signal(s), {len(out['rpc'])} rpc(s)")
57
74
  except Exception as exc: # noqa: BLE001
58
- findings.append({"kind": "compile-error", "line": getattr(exc, "lineno", None) or getattr(exc, "line", None) or 0,
59
- "message": getattr(exc, "pyweb_msg", None) or str(exc)})
75
+ from pyweb.compiler.errors import describe
76
+ rec = describe(exc, src, args.file)
77
+ findings.append({"kind": "compile-error", "line": rec.get("line") or 0, "message": rec["message"],
78
+ "detail": "\n".join(x for x in (rec.get("frame"), rec.get("hint") and "hint: " + rec["hint"],
79
+ rec.get("docs") and "docs: " + rec["docs"]) if x)})
60
80
  for f in findings:
61
81
  print(f"{f['kind']} {f.get('file') or args.file}:{f['line']}: {f['message']}")
82
+ if f.get("detail"):
83
+ print(" " + f["detail"].replace("\n", "\n "))
62
84
  problems = production_problems(src, os.path.dirname(os.path.abspath(args.file))) \
63
85
  if getattr(args, "production", False) else []
64
86
  for p in problems:
@@ -230,32 +252,44 @@ def cmd_build(args):
230
252
  raise SystemExit(2)
231
253
 
232
254
 
233
- DEV_RELOAD_JS = """<script>(()=>{let v=null;async function t(){try{const r=await fetch('/__pyweb/dev/version');const n=await r.text();if(v!==null&&n!==v)location.reload();v=n}catch{}setTimeout(t,600)}t()})()</script>"""
255
+ def _dev_reload_js():
256
+ path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "runtime", "browser", "dev",
257
+ "reload.js")
258
+ with open(path, encoding="utf-8") as fh:
259
+ return "<script>" + fh.read().replace("</", "<\\/") + "</script>"
260
+
261
+
262
+ DEV_RELOAD_JS = _dev_reload_js()
234
263
 
235
264
 
236
265
  def _error_overlay(exc, path):
237
266
  """HTML page shown in the browser while the app has a compile error."""
238
267
  import html as _h
239
- lineno = getattr(exc, "lineno", None)
240
- snippet = ""
268
+ from pyweb.compiler.errors import describe
241
269
  try:
242
- lines = _load(path).splitlines()
243
- if lineno:
244
- lo, hi = max(0, lineno - 4), min(len(lines), lineno + 3)
245
- rows = []
246
- for i in range(lo, hi):
247
- mark = "&gt;" if i + 1 == lineno else "&nbsp;"
248
- rows.append(f"{mark} {i + 1:4d} | {_h.escape(lines[i])}")
249
- snippet = "<pre class=code>" + "\n".join(rows) + "</pre>"
270
+ source = _load(getattr(exc, "filename", None) or path)
250
271
  except OSError:
251
- pass
272
+ source = None
273
+ rec = describe(exc, source, path)
274
+ where = rec.get("file") or path
275
+ if rec.get("line"):
276
+ where += f":{rec['line']}"
277
+ parts = [f"<h1>{_h.escape(type(exc).__name__)}</h1>",
278
+ f"<p class=where>{_h.escape(where)}</p><pre>{_h.escape(rec['message'])}</pre>"]
279
+ if rec.get("frame"):
280
+ parts.append(f"<pre class=code>{_h.escape(rec['frame'])}</pre>")
281
+ if rec.get("hint"):
282
+ parts.append(f"<p class=hint><b>Hint:</b> {_h.escape(rec['hint'])}</p>")
283
+ if rec.get("docs"):
284
+ parts.append(f'<p><a href="{_h.escape(rec["docs"])}" target=_blank rel=noopener>Read the docs</a></p>')
252
285
  return ("<!doctype html><meta charset=utf-8><title>PyWeb error</title>"
253
286
  "<style>body{font:15px/1.5 system-ui;margin:0;background:#1b1b1f;color:#eee}"
254
287
  "main{max-width:880px;margin:8vh auto;padding:0 24px}h1{color:#ff6b6b;font-size:20px}"
255
288
  "pre{background:#0f0f12;padding:16px;border-radius:8px;overflow:auto;font-size:13px}"
256
- ".code{border-left:3px solid #ff6b6b}</style><main>"
257
- f"<h1>{_h.escape(type(exc).__name__)}</h1><pre>{_h.escape(str(exc))}</pre>{snippet}"
258
- "<p>Fix the file and save: this page reloads automatically.</p></main>" + DEV_RELOAD_JS)
289
+ ".code{border-left:3px solid #ff6b6b}.where{color:#aaa;margin:0}a{color:#8ab4ff}"
290
+ ".hint{background:#26262c;padding:10px 14px;border-radius:8px}</style><main>"
291
+ + "".join(parts)
292
+ + "<p>Fix the file and save: this page reloads automatically.</p></main>" + DEV_RELOAD_JS)
259
293
 
260
294
 
261
295
  class _DevState:
@@ -346,7 +380,7 @@ def cmd_dev(args):
346
380
  link = Link(state)
347
381
  _dev_load(state, Site)
348
382
  if state.error is not None:
349
- print(f"error: {state.error}", file=sys.stderr)
383
+ print("error: " + _pretty_error(state.error, state.path), file=sys.stderr)
350
384
  host = getattr(args, "host", "127.0.0.1") or "127.0.0.1"
351
385
 
352
386
  def ready(port):
@@ -395,14 +429,14 @@ def _watch_and_reload(state, Site):
395
429
  def poll():
396
430
  last = snapshot()
397
431
  while True:
398
- time.sleep(0.4)
432
+ time.sleep(0.1)
399
433
  now = snapshot()
400
434
  if now != last:
401
435
  last = now
402
436
  purge_local_modules()
403
437
  _dev_load(state, Site)
404
438
  if state.error is not None:
405
- print(f"error: {state.error}", flush=True)
439
+ print("error: " + _pretty_error(state.error, state.path), flush=True)
406
440
  else:
407
441
  print(f"reloaded {state.path}", flush=True)
408
442
 
@@ -495,13 +529,25 @@ def cmd_serve(args):
495
529
  _net.run(make_site, host=args.host, port=args.port, workers=workers, logger=logger, ready=ready)
496
530
 
497
531
 
532
+ def tests_path(path=None):
533
+ """Where ``pyweb test`` runs pytest: the given file or folder, the folder of a ``.pyweb`` file,
534
+ or the current folder (templates keep ``test_app.py`` next to ``app.pyweb``)."""
535
+ target = os.path.abspath(path or ".")
536
+ if target.endswith(".pyweb"):
537
+ target = os.path.dirname(target)
538
+ return target
539
+
540
+
498
541
  def cmd_test(args):
542
+ import importlib.util
499
543
  import subprocess
500
544
  import sys as _sys
501
- cmd = [_sys.executable, "-m", "pytest", "tests/", "-q"]
502
- if getattr(args, "path", None):
503
- cmd = [_sys.executable, "-m", "pytest", args.path, "-q"]
504
- raise SystemExit(subprocess.call(cmd))
545
+ if importlib.util.find_spec("pytest") is None:
546
+ raise SystemExit("pyweb test runs pytest, which isn't installed: pip install pytest")
547
+ target = tests_path(getattr(args, "path", None))
548
+ if not os.path.exists(target):
549
+ raise SystemExit(f"pyweb test: no such file or folder: {args.path}")
550
+ raise SystemExit(subprocess.call([_sys.executable, "-m", "pytest", target, "-q"]))
505
551
 
506
552
 
507
553
  def cmd_fmt(args):
@@ -983,6 +1029,12 @@ def main(argv=None):
983
1029
  p.add_argument("--token", default=None, help="--http: require Authorization: Bearer TOKEN (or PYWEB_MCP_TOKEN)")
984
1030
  p.set_defaults(fn=cmd_mcp)
985
1031
  p = sub.add_parser("lsp", help="run the language server (stdio) for editors"); p.set_defaults(fn=cmd_lsp)
1032
+ p = sub.add_parser("doctor", help="check the project and the machine, with a fix for each problem")
1033
+ p.add_argument("file", nargs="?", default="app.pyweb")
1034
+ p.add_argument("--production", action="store_true", help="also check the environment for production")
1035
+ p.add_argument("--json", action="store_true", help="print the checks as JSON")
1036
+ p.add_argument("--port", type=int, default=8000, help="the port pyweb dev will use (default 8000)")
1037
+ p.set_defaults(fn=cmd_doctor)
986
1038
  p = sub.add_parser("check"); p.add_argument("file"); p.add_argument("--strict", action="store_true", help="exit 1 on any finding (security warnings too)")
987
1039
  p.add_argument("--production", action="store_true", help="also check the environment is ready for production")
988
1040
  p.set_defaults(fn=cmd_check)
@@ -1036,12 +1088,7 @@ def main(argv=None):
1036
1088
  print(f"error: {exc}", file=sys.stderr)
1037
1089
  raise SystemExit(1)
1038
1090
  except (CompileError, SyntaxError) as exc:
1039
- if isinstance(exc, SyntaxError) and not isinstance(exc, CompileError):
1040
- where = f"{exc.filename or getattr(args, 'file', '')}:{exc.lineno}" if exc.lineno else ""
1041
- msg = getattr(exc, "pyweb_msg", None) or exc.msg
1042
- print(f"error: {where}: {msg}" if where else f"error: {msg}", file=sys.stderr)
1043
- else:
1044
- print(f"error: {exc}", file=sys.stderr)
1091
+ print("error: " + _pretty_error(exc, getattr(args, "file", None)), file=sys.stderr)
1045
1092
  raise SystemExit(1)
1046
1093
 
1047
1094