pyxle-framework 0.2.2__tar.gz → 0.2.4__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 (203) hide show
  1. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/PKG-INFO +1 -1
  2. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/README.md +1 -1
  3. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/faq.md +6 -2
  4. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/getting-started/project-structure.md +17 -7
  5. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/getting-started/quick-start.md +13 -21
  6. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/guides/deployment.md +1 -1
  7. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/guides/middleware.md +8 -0
  8. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/reference/client-api.md +32 -1
  9. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyproject.toml +1 -1
  10. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/cli/init.py +2 -4
  11. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/client/Form.jsx +4 -0
  12. pyxle_framework-0.2.4/pyxle/client/Head.jsx +122 -0
  13. pyxle_framework-0.2.4/pyxle/client/Image.jsx +134 -0
  14. pyxle_framework-0.2.4/pyxle/client/Script.jsx +190 -0
  15. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/client/useAction.jsx +5 -0
  16. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/client/usePathname.jsx +17 -4
  17. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/client_files.py +412 -86
  18. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/ssr/_escape.py +15 -8
  19. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/ssr/render_component.mjs +25 -6
  20. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/ssr/renderer.py +89 -11
  21. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/ssr/ssr_worker.mjs +23 -6
  22. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/ssr/view.py +6 -1
  23. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/ssr/worker_pool.py +9 -0
  24. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_client_files.py +185 -0
  25. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/test_escape.py +34 -4
  26. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/test_renderer.py +51 -1
  27. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/test_view.py +26 -3
  28. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/test_worker_pool.py +58 -0
  29. pyxle_framework-0.2.2/pyxle/client/Head.jsx +0 -34
  30. pyxle_framework-0.2.2/pyxle/client/Image.jsx +0 -27
  31. pyxle_framework-0.2.2/pyxle/client/Script.jsx +0 -19
  32. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/.github/pyxle-logo.svg +0 -0
  33. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/.github/workflows/publish.yml +0 -0
  34. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/.gitignore +0 -0
  35. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/CLAUDE.md +0 -0
  36. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/LICENSE +0 -0
  37. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/Makefile +0 -0
  38. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/README.md +0 -0
  39. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/advanced/compiler-internals.md +0 -0
  40. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/advanced/ssr-pipeline.md +0 -0
  41. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/architecture/README.md +0 -0
  42. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/architecture/build-and-serve.md +0 -0
  43. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/architecture/cli.md +0 -0
  44. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/architecture/compiler.md +0 -0
  45. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/architecture/dev-server.md +0 -0
  46. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/architecture/overview.md +0 -0
  47. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/architecture/parser.md +0 -0
  48. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/architecture/pyxl-files.md +0 -0
  49. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/architecture/routing.md +0 -0
  50. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/architecture/runtime.md +0 -0
  51. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/architecture/ssr.md +0 -0
  52. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/core-concepts/data-loading.md +0 -0
  53. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/core-concepts/layouts.md +0 -0
  54. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/core-concepts/pyxl-files.md +0 -0
  55. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/core-concepts/routing.md +0 -0
  56. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/core-concepts/server-actions.md +0 -0
  57. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/getting-started/installation.md +0 -0
  58. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/guides/api-routes.md +0 -0
  59. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/guides/client-components.md +0 -0
  60. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/guides/editor-setup.md +0 -0
  61. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/guides/environment-variables.md +0 -0
  62. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/guides/error-handling.md +0 -0
  63. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/guides/for-ai-agents.md +0 -0
  64. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/guides/head-management.md +0 -0
  65. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/guides/migration-pyx-to-pyxl.md +0 -0
  66. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/guides/security.md +0 -0
  67. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/guides/styling.md +0 -0
  68. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/reference/cli.md +0 -0
  69. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/reference/configuration.md +0 -0
  70. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/docs/reference/runtime-api.md +0 -0
  71. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/__init__.py +0 -0
  72. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/build/__init__.py +0 -0
  73. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/build/manifest.py +0 -0
  74. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/build/pipeline.py +0 -0
  75. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/build/vite.py +0 -0
  76. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/cli/__init__.py +0 -0
  77. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/cli/assets.py +0 -0
  78. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/cli/logger.py +0 -0
  79. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/cli/scaffold.py +0 -0
  80. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/cli/templates.py +0 -0
  81. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/client/ClientOnly.jsx +0 -0
  82. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/client/index.js +0 -0
  83. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/compiler/__init__.py +0 -0
  84. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/compiler/core.py +0 -0
  85. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/compiler/exceptions.py +0 -0
  86. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/compiler/jsx_imports.py +0 -0
  87. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/compiler/jsx_parser.py +0 -0
  88. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/compiler/model.py +0 -0
  89. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/compiler/parser.py +0 -0
  90. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/compiler/writers.py +0 -0
  91. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/config.py +0 -0
  92. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/__init__.py +0 -0
  93. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/_security.py +0 -0
  94. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/build.py +0 -0
  95. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/builder.py +0 -0
  96. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/csrf.py +0 -0
  97. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/error_pages.py +0 -0
  98. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/layouts.py +0 -0
  99. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/middleware.py +0 -0
  100. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/overlay.py +0 -0
  101. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/path_utils.py +0 -0
  102. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/proxy.py +0 -0
  103. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/registry.py +0 -0
  104. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/route_hooks.py +0 -0
  105. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/routes.py +0 -0
  106. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/scanner.py +0 -0
  107. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/scripts.py +0 -0
  108. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/settings.py +0 -0
  109. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/starlette_app.py +0 -0
  110. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/styles.py +0 -0
  111. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/tailwind.py +0 -0
  112. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/vite.py +0 -0
  113. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/devserver/watcher.py +0 -0
  114. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/env.py +0 -0
  115. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/routing/__init__.py +0 -0
  116. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/routing/paths.py +0 -0
  117. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/runtime/ClientOnly.jsx +0 -0
  118. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/runtime/Head.jsx +0 -0
  119. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/runtime/Image.jsx +0 -0
  120. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/runtime/Script.jsx +0 -0
  121. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/runtime.py +0 -0
  122. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/ssr/__init__.py +0 -0
  123. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/ssr/head_merger.py +0 -0
  124. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/ssr/template.py +0 -0
  125. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/__init__.py +0 -0
  126. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/.gitignore +0 -0
  127. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/__init__.py +0 -0
  128. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/package.json +0 -0
  129. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/pages/api/pulse.py +0 -0
  130. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/pages/index.pyxl +0 -0
  131. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/pages/layout.pyxl +0 -0
  132. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/pages/styles/tailwind.css +0 -0
  133. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/postcss.config.cjs +0 -0
  134. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/public/branding/pyxle-mark.svg +0 -0
  135. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/public/styles/tailwind.css +0 -0
  136. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/pyxle.config.json +0 -0
  137. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/requirements.txt +0 -0
  138. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/pyxle/templates/scaffold/tailwind.config.cjs +0 -0
  139. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/__init__.py +0 -0
  140. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/build/__init__.py +0 -0
  141. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/build/test_manifest.py +0 -0
  142. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/build/test_pipeline.py +0 -0
  143. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/build/test_pipeline_css.py +0 -0
  144. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/build/test_vite.py +0 -0
  145. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/cli/__init__.py +0 -0
  146. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/cli/test_commands.py +0 -0
  147. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/cli/test_logger.py +0 -0
  148. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/cli/test_scaffold.py +0 -0
  149. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/cli/test_templates.py +0 -0
  150. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/__init__.py +0 -0
  151. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_action_compile.py +0 -0
  152. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_action_model.py +0 -0
  153. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_action_parser.py +0 -0
  154. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_action_writers.py +0 -0
  155. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_compile.py +0 -0
  156. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_head_jsx.py +0 -0
  157. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_jsx_imports.py +0 -0
  158. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_jsx_parser.py +0 -0
  159. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_parser.py +0 -0
  160. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_parser_diagnostics.py +0 -0
  161. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_parser_hardening.py +0 -0
  162. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_script_image_detection.py +0 -0
  163. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/compiler/test_script_image_integration.py +0 -0
  164. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/__init__.py +0 -0
  165. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/sample_middlewares.py +0 -0
  166. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_action_routes.py +0 -0
  167. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_build.py +0 -0
  168. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_builder.py +0 -0
  169. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_client_only.py +0 -0
  170. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_csrf.py +0 -0
  171. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_devserver_start.py +0 -0
  172. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_error_pages.py +0 -0
  173. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_layouts.py +0 -0
  174. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_middleware.py +0 -0
  175. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_overlay.py +0 -0
  176. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_proxy.py +0 -0
  177. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_registry.py +0 -0
  178. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_route_error_boundary.py +0 -0
  179. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_route_hooks.py +0 -0
  180. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_routes.py +0 -0
  181. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_scanner.py +0 -0
  182. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_scripts.py +0 -0
  183. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_security_utils.py +0 -0
  184. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_settings.py +0 -0
  185. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_starlette_app.py +0 -0
  186. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_styles.py +0 -0
  187. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_tailwind.py +0 -0
  188. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_vite.py +0 -0
  189. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/devserver/test_watcher.py +0 -0
  190. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/__init__.py +0 -0
  191. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/test_dynamic_head.py +0 -0
  192. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/test_head_merger_extra.py +0 -0
  193. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/test_head_merging.py +0 -0
  194. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/test_integration.py +0 -0
  195. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/test_script_injection.py +0 -0
  196. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/test_template.py +0 -0
  197. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/test_view_error_boundaries.py +0 -0
  198. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/ssr/utils.py +0 -0
  199. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/test_config.py +0 -0
  200. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/test_config_security.py +0 -0
  201. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/test_env.py +0 -0
  202. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/test_runtime.py +0 -0
  203. {pyxle_framework-0.2.2 → pyxle_framework-0.2.4}/tests/test_runtime_errors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyxle-framework
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Python-first full-stack framework with an intelligent CLI.
5
5
  Project-URL: Homepage, https://pyxle.dev
6
6
  Project-URL: Repository, https://github.com/pyxle-framework/pyxle
@@ -2,7 +2,7 @@
2
2
 
3
3
  Pyxle is a Python-first full-stack web framework that brings the Next.js developer experience to the Python ecosystem. Write server logic in Python, UI in React, and ship them together in `.pyxl` files.
4
4
 
5
- **Current version:** 0.2.2 (beta)
5
+ **Current version:** 0.2.4 (beta)
6
6
 
7
7
  ---
8
8
 
@@ -22,7 +22,7 @@ Django and Flask are backend frameworks that render templates. Pyxle is a full-s
22
22
 
23
23
  ### Is Pyxle production-ready?
24
24
 
25
- Pyxle is in **beta** (version 0.2.2). The core features are implemented and tested (1100+ tests, 95%+ coverage), but the API may change before 1.0. Use it for new projects and experiments, but be prepared for breaking changes.
25
+ Pyxle is in **beta** (version 0.2.4). The core features are implemented and tested (1100+ tests, 95%+ coverage), but the API may change before 1.0. Use it for new projects and experiments, but be prepared for breaking changes.
26
26
 
27
27
  ### What Python version do I need?
28
28
 
@@ -193,7 +193,11 @@ Run `pyxle install` or `npm install` in your project directory.
193
193
 
194
194
  ### The page renders without styles
195
195
 
196
- Make sure Tailwind is compiled. Run `npm run dev:css` in a separate terminal, or ensure `pyxle dev` is started with `--tailwind` (the default).
196
+ `pyxle dev` compiles Tailwind through PostCSS automatically when
197
+ `postcss.config.cjs` is present (the scaffold default). If you removed
198
+ that file or are on a custom stylesheet setup, re-check your
199
+ [styling configuration](guides/styling.md) — specifically that the
200
+ relevant CSS is imported from a page or layout.
197
201
 
198
202
  ### Hot reload is not working
199
203
 
@@ -75,11 +75,17 @@ A `.pyxl` file combines Python server logic with a React component. The scaffold
75
75
 
76
76
  ```python
77
77
  # Python section
78
+ from datetime import datetime, timezone
78
79
  from pyxle import __version__
79
80
 
80
81
  @server
81
82
  async def load_home(request):
82
- return {"message": "Hello, world!"}
83
+ now = datetime.now(tz=timezone.utc)
84
+ return {
85
+ "version": __version__,
86
+ "time": now.strftime("%H:%M:%S UTC"),
87
+ "message": "You're ready to build with Pyxle.",
88
+ }
83
89
  ```
84
90
 
85
91
  ```jsx
@@ -88,12 +94,13 @@ import { Head } from 'pyxle/client';
88
94
 
89
95
  export default function HomePage({ data }) {
90
96
  return (
91
- <>
97
+ <main>
92
98
  <Head>
93
- <title>My App</title>
99
+ <title>Pyxle App</title>
94
100
  </Head>
95
101
  <h1>{data.message}</h1>
96
- </>
102
+ <p>Pyxle v{data.version} &middot; {data.time}</p>
103
+ </main>
97
104
  );
98
105
  }
99
106
  ```
@@ -143,9 +150,12 @@ Defines Node.js dependencies and npm scripts:
143
150
  | Script | Purpose |
144
151
  |--------|---------|
145
152
  | `npm run dev` | Start Vite dev server (used internally by `pyxle dev`) |
146
- | `npm run build` | Build CSS + bundle with Vite (used by `pyxle build`) |
147
- | `npm run dev:css` | Watch Tailwind CSS compilation |
148
- | `npm run build:css` | One-shot minified Tailwind build |
153
+ | `npm run build` | Bundle with Vite (used by `pyxle build`); Tailwind is compiled in-pipeline via PostCSS |
154
+
155
+ Tailwind is wired through PostCSS (see `postcss.config.cjs`), so Vite
156
+ handles CSS on both `dev` and `build` without a separate script. If you
157
+ want a standalone Tailwind watcher anyway, see
158
+ [Styling guide → Standalone Tailwind](../guides/styling.md).
149
159
 
150
160
  ### `tailwind.config.cjs`
151
161
 
@@ -24,23 +24,15 @@ pip install -r requirements.txt
24
24
  npm install
25
25
  ```
26
26
 
27
- ## 3. Build Tailwind CSS
28
-
29
- In a separate terminal, start the Tailwind watcher:
30
-
31
- ```bash
32
- npm run dev:css
33
- ```
34
-
35
- This compiles `pages/styles/tailwind.css` into `public/styles/tailwind.css` and watches for changes. The dev server also auto-starts Tailwind if it detects a config file, so this step is optional if you use `pyxle dev` with the `--tailwind` flag (enabled by default).
36
-
37
- ## 4. Start the dev server
27
+ ## 3. Start the dev server
38
28
 
39
29
  ```bash
40
30
  pyxle dev
41
31
  ```
42
32
 
43
- Open [http://localhost:8000](http://localhost:8000) in your browser. You should see the Pyxle landing page with a hero section, feature cards, and a dark mode toggle.
33
+ Open [http://localhost:8000](http://localhost:8000) in your browser. You should see the Pyxle starter page a centered card showing the framework version, server time, and a link to edit `pages/index.pyxl`.
34
+
35
+ Tailwind compiles automatically because the scaffold ships with `postcss.config.cjs` — PostCSS runs as part of the Vite pipeline, so there's nothing separate to start.
44
36
 
45
37
  ## What just happened?
46
38
 
@@ -53,24 +45,24 @@ When you ran `pyxle dev`, the framework:
53
45
  5. **Rendered HTML on the server** -- sent fully-rendered HTML to the browser (SSR)
54
46
  6. **Hydrated on the client** -- React took over the server-rendered HTML for interactivity
55
47
 
56
- ## 5. Make a change
48
+ ## 4. Make a change
57
49
 
58
- Open `pages/index.pyxl` in your editor. Find the `title` value inside the `load_home` function and change it:
50
+ Open `pages/index.pyxl` in your editor. Change the `message` returned by `load_home`:
59
51
 
60
52
  ```python
61
53
  @server
62
54
  async def load_home(request):
55
+ now = datetime.now(tz=timezone.utc)
63
56
  return {
64
- "hero": {
65
- "title": "Hello from Pyxle!",
66
- # ...
67
- },
57
+ "version": __version__,
58
+ "time": now.strftime("%H:%M:%S UTC"),
59
+ "message": "Hello from my Pyxle app!",
68
60
  }
69
61
  ```
70
62
 
71
- Save the file. The browser reloads automatically with your updated title.
63
+ Save the file. The browser reloads automatically with your updated message.
72
64
 
73
- ## 6. Check your routes
65
+ ## 5. Check your routes
74
66
 
75
67
  ```bash
76
68
  pyxle routes
@@ -84,7 +76,7 @@ Route File Loader
84
76
  /api/pulse pages/api/pulse.py --
85
77
  ```
86
78
 
87
- ## 7. Validate your project
79
+ ## 6. Validate your project
88
80
 
89
81
  ```bash
90
82
  pyxle check
@@ -11,7 +11,7 @@ pyxle build
11
11
  This:
12
12
 
13
13
  1. Compiles all `.pyxl` files into Python and JSX modules
14
- 2. Runs `npm run build` (which runs `build:css` for Tailwind, then Vite bundling)
14
+ 2. Runs `npm run build` — Vite bundles JS and processes CSS through PostCSS (Tailwind is compiled in-pipeline when `postcss.config.cjs` is present, which is the scaffold default)
15
15
  3. Outputs production artifacts to the `dist/` directory
16
16
 
17
17
  ### Build options
@@ -98,10 +98,18 @@ The `context` object provides metadata about the matched route:
98
98
  | `target` | `"page" \| "api"` | Route type |
99
99
  | `path` | `str` | URL path pattern |
100
100
  | `source_relative_path` | `Path` | File path relative to project root |
101
+ | `source_absolute_path` | `Path` | Absolute file path on disk |
101
102
  | `module_key` | `str` | Python import key |
103
+ | `content_hash` | `str` | Hash of the compiled route module — changes when the source changes, stable across reloads |
102
104
  | `has_loader` | `bool` | Whether the page has a `@server` loader |
105
+ | `head_elements` | `tuple[str, ...]` | Rendered `<head>` markup registered by the page/layout (SSR only) |
103
106
  | `allowed_methods` | `tuple[str, ...]` | HTTP methods the route handles |
104
107
 
108
+ `RouteContext` is a frozen dataclass — fields are read-only. A shorthand
109
+ `context.as_dict()` returns a JSON-friendly view (keys camelCased, paths
110
+ as POSIX strings) that Pyxle attaches to `request.scope["pyxle"]["route"]`
111
+ for downstream middleware.
112
+
105
113
  ### Built-in hooks
106
114
 
107
115
  Pyxle applies two default hooks:
@@ -6,7 +6,7 @@ All client-side components and hooks are importable from `pyxle/client`:
6
6
  import {
7
7
  Head, Script, Image, ClientOnly,
8
8
  Form, useAction,
9
- Link, navigate, prefetch, refresh
9
+ Link, navigate, prefetch, refresh, usePathname
10
10
  } from 'pyxle/client';
11
11
  ```
12
12
 
@@ -204,6 +204,37 @@ const result = await actionFn(payload);
204
204
 
205
205
  ---
206
206
 
207
+ ### `usePathname()`
208
+
209
+ Reactive hook that returns the current URL pathname and re-renders on
210
+ client-side navigation.
211
+
212
+ ```jsx
213
+ import { usePathname, Link } from 'pyxle/client';
214
+
215
+ function NavLink({ href, children }) {
216
+ const pathname = usePathname();
217
+ const active = pathname === href;
218
+ return (
219
+ <Link href={href} className={active ? 'text-emerald-400' : 'text-zinc-400'}>
220
+ {children}
221
+ </Link>
222
+ );
223
+ }
224
+ ```
225
+
226
+ **Returns:** `string` — the current pathname (e.g. `/dashboard/settings`).
227
+
228
+ **Behaviour:**
229
+ - Reads `window.location.pathname` on the client
230
+ - During SSR, returns the path currently being rendered (via
231
+ `globalThis.__PYXLE_CURRENT_PATHNAME__`) so the first client render matches
232
+ — no hydration mismatch
233
+ - Subscribes to framework navigation events (`Link`, `navigate()`,
234
+ `refresh()`, `popstate`) and re-renders on change
235
+
236
+ ---
237
+
207
238
  ## Functions
208
239
 
209
240
  ### `navigate(path)`
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pyxle-framework"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "Python-first full-stack framework with an intelligent CLI."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -57,11 +57,9 @@ def log_next_steps(
57
57
  if include_install_hint:
58
58
  logger.info(" 2. pyxle install # installs Python + Node dependencies")
59
59
  logger.info(" (or run 'pip install -r requirements.txt' and 'npm install')")
60
- logger.info(" 3. npm run dev:css # watches Tailwind into public/styles/tailwind.css (separate terminal)")
61
- logger.info(" 4. pyxle dev")
62
- else:
63
- logger.info(" 2. npm run dev:css # watches Tailwind into public/styles/tailwind.css (separate terminal)")
64
60
  logger.info(" 3. pyxle dev")
61
+ else:
62
+ logger.info(" 2. pyxle dev")
65
63
 
66
64
 
67
65
  def run_init(
@@ -34,6 +34,10 @@ function resolveActionUrl(actionName, pagePath) {
34
34
  if (!page) {
35
35
  if (typeof window !== 'undefined') {
36
36
  page = window.location.pathname;
37
+ } else if (typeof globalThis.__PYXLE_CURRENT_PATHNAME__ === 'string') {
38
+ // SSR: use the framework-injected request path so the form's action
39
+ // URL matches the one the client will compute at hydration.
40
+ page = globalThis.__PYXLE_CURRENT_PATHNAME__;
37
41
  } else {
38
42
  page = '/';
39
43
  }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * <Head> — declare elements that belong in the document <head>.
3
+ *
4
+ * Works on both tiers of the render pipeline:
5
+ *
6
+ * • SSR — renders children to static markup and registers them
7
+ * with the framework's head registry, so they land in the
8
+ * initial HTML response.
9
+ *
10
+ * • Client — on mount, adopts the equivalent SSR-rendered elements
11
+ * (matched by tag + key attribute) so we don't duplicate
12
+ * them. On update, the adopted nodes are removed and
13
+ * fresh ones inserted, so state-driven head changes (e.g.
14
+ * a dynamic <title>) actually update the document.
15
+ * On unmount, everything this instance owns is removed and
16
+ * the previous <title> is restored.
17
+ *
18
+ * Multiple <Head> components compose — each one owns the nodes it rendered.
19
+ */
20
+ import { useEffect } from 'react';
21
+ import { renderToStaticMarkup } from 'react-dom/server';
22
+
23
+ const OWNER_ATTR = 'data-pyxle-head-client';
24
+ // Key attributes that identify "the same" head element across renders.
25
+ // Order matters: we pick the first one that the declared element has.
26
+ const KEY_ATTRS = ['name', 'property', 'rel', 'href', 'src', 'charset', 'http-equiv'];
27
+
28
+
29
+ function _findEquivalentHeadElement(target) {
30
+ const tag = target.tagName.toLowerCase();
31
+ const keyAttr = KEY_ATTRS.find((a) => target.hasAttribute(a));
32
+ if (!keyAttr) {
33
+ // Without a discriminating attribute we can't safely adopt — caller will
34
+ // insert a fresh copy instead.
35
+ return null;
36
+ }
37
+
38
+ const keyValue = target.getAttribute(keyAttr);
39
+ const escape = (typeof CSS !== 'undefined' && CSS.escape) || ((s) => s);
40
+ const selector = `${tag}[${keyAttr}="${escape(keyValue)}"]:not([${OWNER_ATTR}])`;
41
+
42
+ try {
43
+ return document.head.querySelector(selector);
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+
50
+ function _applyHeadMarkup(markup) {
51
+ if (!markup) return { nodes: [], previousTitle: null };
52
+
53
+ const template = document.createElement('template');
54
+ template.innerHTML = markup;
55
+ const parsed = Array.from(template.content.childNodes).filter(
56
+ (n) => n.nodeType === 1,
57
+ );
58
+
59
+ const nodes = [];
60
+ let previousTitle = null;
61
+
62
+ for (const declared of parsed) {
63
+ if (declared.tagName === 'TITLE') {
64
+ // Only one <title> wins; save the prior value so unmount can restore it.
65
+ if (previousTitle === null) previousTitle = document.title;
66
+ document.title = declared.textContent || '';
67
+ continue;
68
+ }
69
+
70
+ const existing = _findEquivalentHeadElement(declared);
71
+ if (existing) {
72
+ existing.setAttribute(OWNER_ATTR, '');
73
+ nodes.push(existing);
74
+ } else {
75
+ declared.setAttribute(OWNER_ATTR, '');
76
+ document.head.appendChild(declared);
77
+ nodes.push(declared);
78
+ }
79
+ }
80
+
81
+ return { nodes, previousTitle };
82
+ }
83
+
84
+
85
+ export function Head({ children }) {
86
+ // Server-side: register markup with the framework's head registry so
87
+ // the template emits it into the initial HTML.
88
+ if (typeof window === 'undefined') {
89
+ if (typeof globalThis.__PYXLE_HEAD_REGISTRY__ !== 'undefined') {
90
+ try {
91
+ const headMarkup = renderToStaticMarkup(<>{children}</>);
92
+ globalThis.__PYXLE_HEAD_REGISTRY__.register(headMarkup);
93
+ } catch (error) {
94
+ console.error('[Pyxle Head] SSR extraction failed:', error);
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+
100
+ // Client-side: render children to a stable string so we can use it as
101
+ // the effect dependency (children itself changes identity every render).
102
+ let markup = '';
103
+ try {
104
+ markup = renderToStaticMarkup(<>{children}</>);
105
+ } catch (error) {
106
+ console.error('[Pyxle Head] client render failed:', error);
107
+ }
108
+
109
+ useEffect(() => {
110
+ const { nodes, previousTitle } = _applyHeadMarkup(markup);
111
+ return () => {
112
+ for (const node of nodes) {
113
+ if (node.parentNode) node.parentNode.removeChild(node);
114
+ }
115
+ if (previousTitle !== null) {
116
+ document.title = previousTitle;
117
+ }
118
+ };
119
+ }, [markup]);
120
+
121
+ return null;
122
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * <Image> — a thin wrapper around the native <img> with four useful
3
+ * behaviours on top:
4
+ *
5
+ * 1. Native lazy-loading via the standard `loading` attribute,
6
+ * controlled by `priority` / `lazy`.
7
+ * 2. Blur-up placeholder `placeholder="blur"` renders a blurred
8
+ * background image (from `blurDataURL`) or a
9
+ * solid color until the real image loads, then
10
+ * smoothly transitions to sharp.
11
+ * 3. Loading / error state `onLoad` / `onError` callbacks fire once per
12
+ * transition; the element also exposes the
13
+ * state via `data-pyxle-image-state`.
14
+ * 4. Graceful fallback An optional `fallbackSrc` kicks in on error
15
+ * so a broken image URL doesn't leave a blank
16
+ * box (can still be combined with `onError`).
17
+ *
18
+ * Everything else — `srcSet`, `sizes`, `className`, `style`, `onClick`, … —
19
+ * passes straight through to the underlying <img>.
20
+ */
21
+
22
+ import { useEffect, useRef, useState } from 'react';
23
+
24
+ const STATE_LOADING = 'loading';
25
+ const STATE_LOADED = 'loaded';
26
+ const STATE_ERROR = 'error';
27
+
28
+
29
+ export function Image({
30
+ src,
31
+ alt = '',
32
+ width,
33
+ height,
34
+ priority = false,
35
+ lazy = true,
36
+ placeholder = 'empty',
37
+ blurDataURL,
38
+ placeholderColor = '#e5e5e5',
39
+ fallbackSrc,
40
+ onLoad,
41
+ onError,
42
+ className,
43
+ style,
44
+ ...props
45
+ }) {
46
+ const [state, setState] = useState(STATE_LOADING);
47
+ const [currentSrc, setCurrentSrc] = useState(src);
48
+ const imgRef = useRef(null);
49
+
50
+ // Reset loading state when src changes.
51
+ useEffect(() => {
52
+ setState(STATE_LOADING);
53
+ setCurrentSrc(src);
54
+ }, [src]);
55
+
56
+ // If an image is already cached by the browser (common on hover-prefetch
57
+ // or client-side navigation), the `load` event never fires on the new
58
+ // element. Check `complete` after mount and sync state manually.
59
+ //
60
+ // Symmetrically: when SSR renders an `<img>` with a broken `src`, the
61
+ // browser may finish the failed fetch before React hydrates, meaning the
62
+ // synthetic `onError` listener is attached too late to see the event. In
63
+ // that case `complete` is still true but `naturalWidth` is 0 — treat as
64
+ // error and drive the fallback / onError path.
65
+ useEffect(() => {
66
+ const el = imgRef.current;
67
+ if (!el || !el.complete || state !== STATE_LOADING) return;
68
+
69
+ if (el.naturalWidth > 0) {
70
+ setState(STATE_LOADED);
71
+ if (onLoad) onLoad({ nativeEvent: null, target: el, fromCache: true });
72
+ } else {
73
+ // Image finished fetching but has no pixels — treat as error.
74
+ if (fallbackSrc && currentSrc !== fallbackSrc) {
75
+ setCurrentSrc(fallbackSrc);
76
+ } else {
77
+ setState(STATE_ERROR);
78
+ if (onError) onError({ nativeEvent: null, target: el });
79
+ }
80
+ }
81
+ // Only run on mount and when src changes; callbacks intentionally omitted.
82
+ // eslint-disable-next-line react-hooks/exhaustive-deps
83
+ }, [currentSrc]);
84
+
85
+ function handleLoad(event) {
86
+ setState(STATE_LOADED);
87
+ if (onLoad) onLoad(event);
88
+ }
89
+
90
+ function handleError(event) {
91
+ if (fallbackSrc && currentSrc !== fallbackSrc) {
92
+ setCurrentSrc(fallbackSrc);
93
+ return; // wait for the fallback to load / fail
94
+ }
95
+ setState(STATE_ERROR);
96
+ if (onError) onError(event);
97
+ }
98
+
99
+ const showPlaceholder = placeholder === 'blur' && state === STATE_LOADING;
100
+
101
+ const mergedStyle = {
102
+ ...(showPlaceholder
103
+ ? {
104
+ backgroundColor: blurDataURL ? undefined : placeholderColor,
105
+ backgroundImage: blurDataURL ? `url("${blurDataURL}")` : undefined,
106
+ backgroundSize: 'cover',
107
+ backgroundPosition: 'center',
108
+ backgroundRepeat: 'no-repeat',
109
+ filter: blurDataURL ? 'blur(20px)' : undefined,
110
+ }
111
+ : {}),
112
+ // Smooth transition once the image renders on top.
113
+ transition: placeholder === 'blur' ? 'filter 250ms ease-out' : undefined,
114
+ ...style,
115
+ };
116
+
117
+ return (
118
+ <img
119
+ ref={imgRef}
120
+ src={currentSrc}
121
+ alt={alt}
122
+ width={width}
123
+ height={height}
124
+ loading={priority ? 'eager' : lazy ? 'lazy' : 'eager'}
125
+ decoding={priority ? 'sync' : 'async'}
126
+ onLoad={handleLoad}
127
+ onError={handleError}
128
+ className={className}
129
+ style={mergedStyle}
130
+ data-pyxle-image-state={state}
131
+ {...props}
132
+ />
133
+ );
134
+ }