quantum-framework 0.16.0__tar.gz → 0.18.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. {quantum_framework-0.16.0/quantum_framework.egg-info → quantum_framework-0.18.0}/PKG-INFO +1 -1
  2. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/pyproject.toml +1 -1
  3. quantum_framework-0.18.0/quantum/cli/check.py +223 -0
  4. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/runner.py +9 -0
  5. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/ast_nodes.py +6 -0
  6. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/ui_engine/src/ast_nodes.py +27 -1
  7. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/ui_engine/src/parser.py +56 -3
  8. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parser.py +62 -17
  9. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/data/query_parser.py +21 -1
  10. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/forms/action_parser.py +15 -0
  11. quantum_framework-0.18.0/quantum/core/xml_lines.py +81 -0
  12. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/action_handler.py +89 -24
  13. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/component.py +6 -1
  14. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/database_service.py +5 -0
  15. quantum_framework-0.18.0/quantum/runtime/db_schema.py +100 -0
  16. quantum_framework-0.18.0/quantum/runtime/dev_panel.py +178 -0
  17. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executor_registry.py +8 -1
  18. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/data/query_executor.py +38 -4
  19. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/renderer.py +11 -0
  20. quantum_framework-0.18.0/quantum/runtime/table_params.py +163 -0
  21. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_console.py +48 -3
  22. quantum_framework-0.18.0/quantum/runtime/ui_form_rules.py +193 -0
  23. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_html_adapter.py +1 -1
  24. quantum_framework-0.18.0/quantum/runtime/ui_page_renderer.py +392 -0
  25. quantum_framework-0.18.0/quantum/runtime/ui_pager.py +73 -0
  26. quantum_framework-0.18.0/quantum/runtime/ui_table_edit.py +139 -0
  27. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_view.py +76 -10
  28. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/web_server.py +234 -22
  29. {quantum_framework-0.16.0 → quantum_framework-0.18.0/quantum_framework.egg-info}/PKG-INFO +1 -1
  30. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_framework.egg-info/SOURCES.txt +8 -0
  31. quantum_framework-0.16.0/quantum/runtime/ui_page_renderer.py +0 -226
  32. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/LICENSE +0 -0
  33. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/README.md +0 -0
  34. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/__init__.py +0 -0
  35. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/__init__.py +0 -0
  36. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/admin.py +0 -0
  37. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/__init__.py +0 -0
  38. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/build.py +0 -0
  39. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/dev.py +0 -0
  40. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/docs.py +0 -0
  41. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/lint.py +0 -0
  42. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/migrate.py +0 -0
  43. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/new.py +0 -0
  44. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/serve.py +0 -0
  45. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/commands/test.py +0 -0
  46. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/deploy.py +0 -0
  47. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/hot_reload.py +0 -0
  48. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/jobs.py +0 -0
  49. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/mq.py +0 -0
  50. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/pkg.py +0 -0
  51. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/server_process.py +0 -0
  52. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/cli/utils.py +0 -0
  53. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/__init__.py +0 -0
  54. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/base_generator.py +0 -0
  55. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/cli.py +0 -0
  56. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/expression_transformer.py +0 -0
  57. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/javascript/__init__.py +0 -0
  58. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/javascript/generator.py +0 -0
  59. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/optimizer.py +0 -0
  60. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/python/__init__.py +0 -0
  61. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/python/generator.py +0 -0
  62. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/python/runtime.py +0 -0
  63. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/compiler/transpiler.py +0 -0
  64. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/__init__.py +0 -0
  65. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/config_env.py +0 -0
  66. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/docs_links.py +0 -0
  67. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/expression_diagnostics.py +0 -0
  68. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/expression_stdlib.py +0 -0
  69. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/expressions.py +0 -0
  70. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/agents/src/__init__.py +0 -0
  71. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/agents/src/ast_node.py +0 -0
  72. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/conditionals/src/__init__.py +0 -0
  73. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/conditionals/src/ast_node.py +0 -0
  74. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/data_import/src/__init__.py +0 -0
  75. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/data_import/src/ast_node.py +0 -0
  76. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/data_import/src/runtime.py +0 -0
  77. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/dump/src/__init__.py +0 -0
  78. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/dump/src/ast_node.py +0 -0
  79. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/dump/src/parser.py +0 -0
  80. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/dump/src/runtime.py +0 -0
  81. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/functions/src/__init__.py +0 -0
  82. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/functions/src/ast_node.py +0 -0
  83. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/game_engine_2d/src/__init__.py +0 -0
  84. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/game_engine_2d/src/ast_nodes.py +0 -0
  85. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/game_engine_2d/src/parser.py +0 -0
  86. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/invocation/src/__init__.py +0 -0
  87. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/invocation/src/ast_node.py +0 -0
  88. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/invocation/src/runtime.py +0 -0
  89. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/knowledge_base/src/__init__.py +0 -0
  90. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/knowledge_base/src/ast_node.py +0 -0
  91. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/knowledge_base/src/parser.py +0 -0
  92. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/logging/src/__init__.py +0 -0
  93. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/logging/src/ast_node.py +0 -0
  94. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/logging/src/parser.py +0 -0
  95. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/logging/src/runtime.py +0 -0
  96. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/loops/src/__init__.py +0 -0
  97. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/loops/src/ast_node.py +0 -0
  98. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/query/src/__init__.py +0 -0
  99. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/query/src/query_validators.py +0 -0
  100. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/state_management/src/__init__.py +0 -0
  101. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/state_management/src/ast_node.py +0 -0
  102. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/terminal_engine/src/__init__.py +0 -0
  103. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/terminal_engine/src/ast_nodes.py +0 -0
  104. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/terminal_engine/src/parser.py +0 -0
  105. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/testing_engine/src/__init__.py +0 -0
  106. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/testing_engine/src/ast_nodes.py +0 -0
  107. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/testing_engine/src/parser.py +0 -0
  108. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/theming/src/__init__.py +0 -0
  109. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/theming/src/ast_node.py +0 -0
  110. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/theming/src/presets.py +0 -0
  111. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/ui_engine/src/__init__.py +0 -0
  112. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/websocket/src/__init__.py +0 -0
  113. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/features/websocket/src/ast_node.py +0 -0
  114. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/guards.py +0 -0
  115. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/html_compat.py +0 -0
  116. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parser_registry.py +0 -0
  117. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/__init__.py +0 -0
  118. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/ai/__init__.py +0 -0
  119. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/ai/agent_parser.py +0 -0
  120. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/ai/knowledge_parser.py +0 -0
  121. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/ai/llm_parser.py +0 -0
  122. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/ai/team_parser.py +0 -0
  123. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/base.py +0 -0
  124. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/composition/__init__.py +0 -0
  125. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/composition/import_parser.py +0 -0
  126. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/composition/slot_parser.py +0 -0
  127. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/control_flow/__init__.py +0 -0
  128. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/control_flow/if_parser.py +0 -0
  129. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/control_flow/loop_parser.py +0 -0
  130. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/control_flow/set_parser.py +0 -0
  131. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/data/__init__.py +0 -0
  132. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/data/data_parser.py +0 -0
  133. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/data/invoke_parser.py +0 -0
  134. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/data/transaction_parser.py +0 -0
  135. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/events/__init__.py +0 -0
  136. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/events/dispatch_event_parser.py +0 -0
  137. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/forms/__init__.py +0 -0
  138. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/forms/flash_parser.py +0 -0
  139. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/forms/redirect_parser.py +0 -0
  140. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/functions/__init__.py +0 -0
  141. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/functions/function_parser.py +0 -0
  142. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/functions/param_parser.py +0 -0
  143. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/functions/return_parser.py +0 -0
  144. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/html/__init__.py +0 -0
  145. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/html/component_call_parser.py +0 -0
  146. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/html/html_parser.py +0 -0
  147. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/jobs/__init__.py +0 -0
  148. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/jobs/job_parser.py +0 -0
  149. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/jobs/schedule_parser.py +0 -0
  150. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/jobs/thread_parser.py +0 -0
  151. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/messaging/__init__.py +0 -0
  152. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/messaging/message_ack_parser.py +0 -0
  153. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/messaging/message_nack_parser.py +0 -0
  154. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/messaging/message_parser.py +0 -0
  155. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/messaging/queue_parser.py +0 -0
  156. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/messaging/websocket_parser.py +0 -0
  157. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/routing/__init__.py +0 -0
  158. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/routing/route_parser.py +0 -0
  159. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/scripting/__init__.py +0 -0
  160. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/scripting/pyclass_parser.py +0 -0
  161. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/scripting/pydecorator_parser.py +0 -0
  162. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/scripting/pyimport_parser.py +0 -0
  163. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/scripting/python_parser.py +0 -0
  164. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/services/__init__.py +0 -0
  165. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/services/dump_parser.py +0 -0
  166. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/services/file_parser.py +0 -0
  167. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/services/log_parser.py +0 -0
  168. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/parsers/services/mail_parser.py +0 -0
  169. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/core/tiers.py +0 -0
  170. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/packages/__init__.py +0 -0
  171. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/packages/manager.py +0 -0
  172. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/packages/manifest.py +0 -0
  173. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/packages/registry.py +0 -0
  174. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/packages/resolver.py +0 -0
  175. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/plugins/__init__.py +0 -0
  176. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/plugins/hooks.py +0 -0
  177. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/plugins/loader.py +0 -0
  178. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/plugins/manifest.py +0 -0
  179. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/plugins/registry.py +0 -0
  180. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/__init__.py +0 -0
  181. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/adapters/__init__.py +0 -0
  182. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/adapters/memory_adapter.py +0 -0
  183. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/adapters/rabbitmq_adapter.py +0 -0
  184. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/adapters/redis_adapter.py +0 -0
  185. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/adapters/sqlite_adapter.py +0 -0
  186. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/agent_service.py +0 -0
  187. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ast_cache.py +0 -0
  188. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/auth_service.py +0 -0
  189. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/component_composer.py +0 -0
  190. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/component_resolver.py +0 -0
  191. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/conditions.py +0 -0
  192. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/email_service.py +0 -0
  193. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/error_handler.py +0 -0
  194. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/execution_context.py +0 -0
  195. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/__init__.py +0 -0
  196. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/ai/__init__.py +0 -0
  197. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/ai/agent_executor.py +0 -0
  198. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/ai/knowledge_executor.py +0 -0
  199. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/ai/llm_executor.py +0 -0
  200. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/ai/team_executor.py +0 -0
  201. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/base.py +0 -0
  202. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/control_flow/__init__.py +0 -0
  203. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/control_flow/if_executor.py +0 -0
  204. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/control_flow/loop_executor.py +0 -0
  205. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/control_flow/redirect_executor.py +0 -0
  206. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/control_flow/set_executor.py +0 -0
  207. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/data/__init__.py +0 -0
  208. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/data/data_executor.py +0 -0
  209. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/data/invoke_executor.py +0 -0
  210. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/data/transaction_executor.py +0 -0
  211. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/jobs/__init__.py +0 -0
  212. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/jobs/job_executor.py +0 -0
  213. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/jobs/schedule_executor.py +0 -0
  214. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/jobs/thread_executor.py +0 -0
  215. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/messaging/__init__.py +0 -0
  216. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/messaging/message_ack_executor.py +0 -0
  217. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/messaging/message_executor.py +0 -0
  218. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/messaging/queue_executor.py +0 -0
  219. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/messaging/websocket_executor.py +0 -0
  220. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/scripting/__init__.py +0 -0
  221. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/scripting/pyclass_executor.py +0 -0
  222. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/scripting/pyimport_executor.py +0 -0
  223. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/scripting/python_executor.py +0 -0
  224. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/services/__init__.py +0 -0
  225. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/services/dump_executor.py +0 -0
  226. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/services/file_executor.py +0 -0
  227. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/services/log_executor.py +0 -0
  228. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/executors/services/mail_executor.py +0 -0
  229. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/expression_cache.py +0 -0
  230. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/file_upload_service.py +0 -0
  231. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/function_registry.py +0 -0
  232. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/game_builder.py +0 -0
  233. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/game_code_generator.py +0 -0
  234. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/game_templates.py +0 -0
  235. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/godot_code_generator.py +0 -0
  236. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/godot_templates.py +0 -0
  237. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/job_executor.py +0 -0
  238. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/knowledge_service.py +0 -0
  239. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/llm_cache.py +0 -0
  240. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/llm_providers.py +0 -0
  241. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/llm_service.py +0 -0
  242. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/local_server.py +0 -0
  243. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/logging_setup.py +0 -0
  244. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/message_broker.py +0 -0
  245. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/message_queue_service.py +0 -0
  246. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/param_validation.py +0 -0
  247. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/pypy_compat.py +0 -0
  248. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/python_bridge.py +0 -0
  249. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/query_validators.py +0 -0
  250. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/service_container.py +0 -0
  251. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/terminal_builder.py +0 -0
  252. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/terminal_code_generator.py +0 -0
  253. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/terminal_templates.py +0 -0
  254. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/testing_builder.py +0 -0
  255. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/testing_code_generator.py +0 -0
  256. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/testing_templates.py +0 -0
  257. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_builder.py +0 -0
  258. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_desktop.py +0 -0
  259. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_html_templates.py +0 -0
  260. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_mobile_adapter.py +0 -0
  261. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_mobile_templates.py +0 -0
  262. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_textual_adapter.py +0 -0
  263. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_textual_templates.py +0 -0
  264. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_tokens.py +0 -0
  265. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/ui_validator.py +0 -0
  266. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/validators.py +0 -0
  267. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/websocket_adapter.py +0 -0
  268. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/websocket_service.py +0 -0
  269. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/websocket_transport.py +0 -0
  270. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/runtime/wsgi.py +0 -0
  271. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/services.py +0 -0
  272. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum/utils/__init__.py +0 -0
  273. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/__init__.py +0 -0
  274. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/assets/quantum-admin-connectors.js +0 -0
  275. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/assets/quantum-admin.css +0 -0
  276. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/assets/quantum-source-highlight.js +0 -0
  277. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/__init__.py +0 -0
  278. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/audit_service.py +0 -0
  279. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/auth_service.py +0 -0
  280. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/component_discovery.py +0 -0
  281. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/config_generator.py +0 -0
  282. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/connector_service.py +0 -0
  283. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/crud.py +0 -0
  284. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/database.py +0 -0
  285. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/db_setup_service.py +0 -0
  286. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/deploy_service.py +0 -0
  287. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/docker_service.py +0 -0
  288. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/environment_service.py +0 -0
  289. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/git_service.py +0 -0
  290. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/health_service.py +0 -0
  291. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/job_service.py +0 -0
  292. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/log_service.py +0 -0
  293. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/main.py +0 -0
  294. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/models.py +0 -0
  295. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/pipeline_service.py +0 -0
  296. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/resource_manager.py +0 -0
  297. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/schemas.py +0 -0
  298. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/secret_manager.py +0 -0
  299. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/settings_service.py +0 -0
  300. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/template_service.py +0 -0
  301. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/test_execution_service.py +0 -0
  302. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/test_generator.py +0 -0
  303. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/webhook_service.py +0 -0
  304. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/backend/websocket_manager.py +0 -0
  305. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/_layout/AdminShell.q +0 -0
  306. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/agents.q +0 -0
  307. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/app/[name].q +0 -0
  308. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/applications.q +0 -0
  309. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/component/[...path].q +0 -0
  310. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/components.q +0 -0
  311. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/connectors.q +0 -0
  312. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/dashboard.q +0 -0
  313. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/database.q +0 -0
  314. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/features.q +0 -0
  315. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/index.q +0 -0
  316. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/jobs.q +0 -0
  317. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/login.q +0 -0
  318. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/logout.q +0 -0
  319. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/settings.q +0 -0
  320. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/source.q +0 -0
  321. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/components/admin/tests.q +0 -0
  322. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/run.py +0 -0
  323. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/__init__.py +0 -0
  324. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/_base.py +0 -0
  325. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/_component_test_generator.py +0 -0
  326. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/_yaml_editor.py +0 -0
  327. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/apps.py +0 -0
  328. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/auth.py +0 -0
  329. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/components.py +0 -0
  330. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/connectors.py +0 -0
  331. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/projects.py +0 -0
  332. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/repository.py +0 -0
  333. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/settings.py +0 -0
  334. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_admin/services/yaml_import.py +0 -0
  335. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_framework.egg-info/dependency_links.txt +0 -0
  336. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_framework.egg-info/entry_points.txt +0 -0
  337. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_framework.egg-info/requires.txt +0 -0
  338. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/quantum_framework.egg-info/top_level.txt +0 -0
  339. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/setup.cfg +0 -0
  340. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_agent.py +0 -0
  341. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_animations.py +0 -0
  342. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_ast_cache.py +0 -0
  343. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_cli_commands.py +0 -0
  344. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_component_library.py +0 -0
  345. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_expression_cache.py +0 -0
  346. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_form_validation.py +0 -0
  347. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_game_codegen.py +0 -0
  348. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_game_e2e.py +0 -0
  349. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_game_engine.py +0 -0
  350. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_game_parser.py +0 -0
  351. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_godot_codegen.py +0 -0
  352. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_job_executor.py +0 -0
  353. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_llm_providers.py +0 -0
  354. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_message_broker.py +0 -0
  355. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_message_queue.py +0 -0
  356. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_mobile_target.py +0 -0
  357. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_multi_agent.py +0 -0
  358. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_packages.py +0 -0
  359. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_plugins.py +0 -0
  360. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_pypy_compat.py +0 -0
  361. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_python_scripting.py +0 -0
  362. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_terminal_codegen.py +0 -0
  363. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_terminal_parser.py +0 -0
  364. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_testing_codegen.py +0 -0
  365. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_testing_parser.py +0 -0
  366. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_theming.py +0 -0
  367. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_ui_html_adapter.py +0 -0
  368. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_ui_parity.py +0 -0
  369. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_ui_parser.py +0 -0
  370. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_ui_textual_adapter.py +0 -0
  371. {quantum_framework-0.16.0 → quantum_framework-0.18.0}/tests/test_websocket.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantum-framework
3
- Version: 0.16.0
3
+ Version: 0.18.0
4
4
  Summary: Full-stack declarative framework for building web, desktop, and mobile applications
5
5
  Author-email: Daniel Gregorio <danielgregorio2611@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quantum-framework"
7
- version = "0.16.0"
7
+ version = "0.18.0"
8
8
  description = "Full-stack declarative framework for building web, desktop, and mobile applications"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,223 @@
1
+ """`quantum check`: every page parses, every q:query compiles against the database,
2
+ and every `{query.field}` a page reads is a column the query returns (M8, DEV-3).
3
+
4
+ A typo in SQL or in a field name used to surface only when someone opened the
5
+ page — `no such column` as a 500, or, for a field, an empty cell. This reads
6
+ the project the way the server would and reports each problem with its file
7
+ and line, without running anything: a query is compiled by the database
8
+ (EXPLAIN) and a SELECT's columns come from running it with LIMIT 0.
9
+ """
10
+
11
+ import re
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import Dict, Iterator, List, Optional
15
+
16
+ # Fields of `<name>_result` and of a query row that are not columns.
17
+ _META = {'recordCount', 'columnList', 'executionTime', 'success', 'error', 'sql', 'page',
18
+ 'totalPages', 'hasMore', 'cached', 'affectedRows', 'lastInsertId', 'data', 'length'}
19
+ _EXPR = re.compile(r'\{([^{}]*)\}')
20
+
21
+
22
+ @dataclass
23
+ class Problem:
24
+ file: str
25
+ line: Optional[int]
26
+ message: str
27
+
28
+ def __str__(self) -> str:
29
+ return f'{self.file}:{self.line or "?"}: {self.message}'
30
+
31
+
32
+ def _config(config_path: Path) -> dict:
33
+ import yaml
34
+ from quantum.core.config_env import expand_env
35
+ if not config_path.exists():
36
+ return {}
37
+ return expand_env(yaml.safe_load(config_path.read_text(encoding='utf-8')) or {})
38
+
39
+
40
+ def _children(node) -> Iterator:
41
+ """The nodes inside a node, whatever the field is called."""
42
+ for campo in ('statements', 'body', 'if_body', 'else_body', 'children'):
43
+ for filho in getattr(node, campo, None) or []:
44
+ if hasattr(filho, '__dict__'):
45
+ yield filho
46
+ for bloco in getattr(node, 'elseif_blocks', None) or []:
47
+ corpo = bloco.get('body') if isinstance(bloco, dict) else getattr(bloco, 'body', None)
48
+ for filho in corpo or []:
49
+ yield filho
50
+
51
+
52
+ def _texts(node) -> List[str]:
53
+ """The strings of a node that may hold {expressions}."""
54
+ textos = []
55
+ for campo, valor in vars(node).items():
56
+ if campo.startswith('_') or campo in ('sql',):
57
+ continue
58
+ if isinstance(valor, str) and '{' in valor:
59
+ textos.append(valor)
60
+ elif isinstance(valor, dict):
61
+ textos += [v for v in valor.values() if isinstance(v, str) and '{' in v]
62
+ return textos
63
+
64
+
65
+ class ProjectChecker:
66
+ def __init__(self, project: Path, config_path: Path):
67
+ self.project = project
68
+ self.config = _config(config_path)
69
+ self.datasources = self.config.get('datasources') or {}
70
+ self._conexoes: Dict[str, object] = {}
71
+ self.problems: List[Problem] = []
72
+ self.notes: List[str] = []
73
+ self.queries_checked = 0
74
+ self.files = 0
75
+
76
+ # -- the database ---------------------------------------------------------
77
+
78
+ def _connection(self, nome: str):
79
+ from quantum.runtime.db_schema import SchemaError, connect_readonly
80
+ if nome not in self._conexoes:
81
+ try:
82
+ self._conexoes[nome] = connect_readonly(self.datasources[nome], self.project)
83
+ except SchemaError as exc:
84
+ self._conexoes[nome] = None
85
+ self.notes.append(f'datasource {nome!r} not checked: {exc}')
86
+ return self._conexoes[nome]
87
+
88
+ # -- a file ---------------------------------------------------------------
89
+
90
+ def check_file(self, arquivo: Path) -> None:
91
+ from quantum.core.parser import QuantumParser
92
+ rel = arquivo.relative_to(self.project).as_posix() if arquivo.is_relative_to(self.project) else str(arquivo)
93
+ self.files += 1
94
+ try:
95
+ ast = QuantumParser(use_cache=False).parse_file(str(arquivo))
96
+ except Exception as exc: # a parse error is a problem like any other
97
+ primeira = str(exc).split('\n')[0]
98
+ self.problems.append(Problem(rel, getattr(exc, 'line', None), primeira))
99
+ return
100
+ colunas: Dict[str, List[str]] = {}
101
+ self._queries(ast, rel, colunas)
102
+ self._fields(ast, rel, colunas, {nome: nome for nome in colunas}, None)
103
+
104
+ def _queries(self, node, rel: str, colunas: Dict[str, List[str]]) -> None:
105
+ from quantum.core.ast_nodes import ActionNode, QueryNode
106
+ from quantum.runtime.db_schema import check_sql, read_schema
107
+ for filho in _children(node):
108
+ if isinstance(filho, ActionNode) and getattr(filho, 'table', None):
109
+ # UI-10: the table the action takes its params from exists, with those columns.
110
+ ds = filho.table_datasource
111
+ if ds not in self.datasources:
112
+ self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
113
+ f'<q:action name="{filho.name}">: datasource {ds!r} is not '
114
+ f'declared in quantum.config.yaml'))
115
+ elif self._connection(ds) is not None:
116
+ esquema = read_schema(self._connection(ds))
117
+ tabela = esquema.get(filho.table)
118
+ if tabela is None:
119
+ self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
120
+ f'<q:action name="{filho.name}" table="{filho.table}">: '
121
+ f'no such table (tables: {", ".join(esquema) or "none"})'))
122
+ else:
123
+ for coluna in filho.table_columns or []:
124
+ if coluna not in tabela.columns:
125
+ self.problems.append(Problem(
126
+ rel, getattr(filho, 'source_line', None),
127
+ f'<q:action name="{filho.name}" columns=...>: table "{filho.table}" '
128
+ f'has no column "{coluna}"'))
129
+ if isinstance(filho, QueryNode):
130
+ ds = filho.datasource
131
+ if not ds or ds.startswith('knowledge:') or getattr(filho, 'source', None):
132
+ pass # RAG or query of queries: not SQL on a database
133
+ elif ds not in self.datasources:
134
+ self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
135
+ f'<q:query name="{filho.name}">: datasource {ds!r} is not '
136
+ f'declared in quantum.config.yaml'))
137
+ else:
138
+ conexao = self._connection(ds)
139
+ if conexao is not None:
140
+ self.queries_checked += 1
141
+ erro, cols = check_sql(conexao, filho.sql or '')
142
+ if erro:
143
+ self.problems.append(Problem(rel, getattr(filho, 'source_line', None),
144
+ f'<q:query name="{filho.name}">: {erro}'))
145
+ elif cols is not None:
146
+ colunas[filho.name] = cols
147
+ self._queries(filho, rel, colunas)
148
+
149
+ def _fields(self, node, rel: str, colunas: Dict[str, List[str]], ligados: Dict[str, str],
150
+ linha: Optional[int]) -> None:
151
+ """Each `{var.field}` where var is a query, or a row of one, names a column it returns."""
152
+ from quantum.core.features.loops.src.ast_node import LoopNode
153
+ from quantum.core.features.ui_engine.src.ast_nodes import UIColumnNode, UIListNode, UITableNode
154
+ linha = getattr(node, 'source_line', None) or linha
155
+ for texto in _texts(node):
156
+ for expressao in _EXPR.findall(texto):
157
+ for var, campo in re.findall(r'(?<![\w.])(\w+)\.(\w+)', expressao):
158
+ self._field(rel, linha, colunas, ligados, var, campo)
159
+ if isinstance(node, UIColumnNode) and node.key and '$tabela' in ligados:
160
+ self._field(rel, linha, colunas, ligados, '$tabela', node.key, f'<ui:column key="{node.key}">')
161
+
162
+ for filho in _children(node):
163
+ dentro = dict(ligados)
164
+ origem = None
165
+ if isinstance(filho, LoopNode):
166
+ origem = filho.query_name if filho.loop_type == 'query' else _single_name(filho.items)
167
+ if origem in colunas:
168
+ dentro[filho.var_name] = origem
169
+ elif isinstance(filho, (UITableNode, UIListNode)):
170
+ origem = _single_name(filho.source)
171
+ if origem in colunas:
172
+ dentro[filho.as_var or ('row' if isinstance(filho, UITableNode) else 'item')] = origem
173
+ if isinstance(filho, UITableNode):
174
+ dentro['$tabela'] = origem
175
+ self._fields(filho, rel, colunas, dentro, linha)
176
+
177
+ def _field(self, rel, linha, colunas, ligados, var, campo, como=None) -> None:
178
+ consulta = ligados.get(var)
179
+ if consulta is None or campo in _META:
180
+ return
181
+ cols = colunas.get(consulta, [])
182
+ if campo not in cols:
183
+ quem = como or f'{{{var}.{campo}}}'
184
+ self.problems.append(Problem(
185
+ rel, linha, f'{quem}: query "{consulta}" returns no column "{campo}" '
186
+ f'(columns: {", ".join(cols) or "none"})'))
187
+
188
+ # -- the project ----------------------------------------------------------
189
+
190
+ def run(self) -> List[Problem]:
191
+ pasta = self.project / (self.config.get('paths', {}) or {}).get('components', 'components')
192
+ if not pasta.exists():
193
+ self.notes.append(f'no components folder at {pasta}')
194
+ return self.problems
195
+ for arquivo in sorted(pasta.rglob('*.q')):
196
+ self.check_file(arquivo)
197
+ for conexao in self._conexoes.values():
198
+ if conexao is not None:
199
+ conexao.close()
200
+ return self.problems
201
+
202
+
203
+ def _single_name(expr: Optional[str]) -> Optional[str]:
204
+ """`{tarefas}` -> 'tarefas'; anything more complex -> None."""
205
+ m = re.fullmatch(r'\s*\{\s*(\w+)\s*\}\s*', expr or '')
206
+ return m.group(1) if m else None
207
+
208
+
209
+ def run_check(config_path: str = 'quantum.config.yaml') -> int:
210
+ """`quantum check`: print each problem as file:line: message; exit 1 if there is any."""
211
+ caminho = Path(config_path).resolve()
212
+ checker = ProjectChecker(caminho.parent, caminho)
213
+ problemas = checker.run()
214
+ for nota in checker.notes:
215
+ print(f'[NOTE] {nota}')
216
+ for problema in problemas:
217
+ print(problema)
218
+ resumo = (f'{checker.files} file(s), {checker.queries_checked} query(ies) checked against the database')
219
+ if problemas:
220
+ print(f'\n{len(problemas)} problem(s) — {resumo}.')
221
+ return 1
222
+ print(f'OK — {resumo}.')
223
+ return 0
@@ -287,6 +287,7 @@ Examples:
287
287
  quantum admin # Start the Quantum Admin
288
288
  quantum console # The application's pages in the terminal
289
289
  quantum desktop # The application's pages in a desktop window
290
+ quantum check # Pages parse, SQL compiles, query fields exist
290
291
  quantum run game.q --engine godot # Build Godot 4 project
291
292
  quantum run backup-job.q # Execute job
292
293
  quantum deploy ./my-app # Deploy application
@@ -342,6 +343,10 @@ Examples:
342
343
  console_parser.add_argument('path', nargs='?', default='/', help='Page to open (default: /)')
343
344
  console_parser.add_argument('--config', default='quantum.config.yaml', help='Config file')
344
345
 
346
+ # Check: pages parse, queries compile, fields exist (M8, DEV-3)
347
+ check_parser = subparsers.add_parser('check', help='Check pages, SQL and query fields against the database')
348
+ check_parser.add_argument('--config', default='quantum.config.yaml', help='Config file')
349
+
345
350
  # Desktop: the application's pages in a native window (UI-4)
346
351
  desktop_parser = subparsers.add_parser('desktop', help="Open the application's pages in a desktop window")
347
352
  desktop_parser.add_argument('path', nargs='?', default='/', help='Page to open (default: /)')
@@ -419,6 +424,10 @@ Examples:
419
424
  from quantum.runtime.ui_console import run_console
420
425
  sys.exit(run_console(args.config, args.path))
421
426
 
427
+ elif args.command == 'check':
428
+ from quantum.cli.check import run_check
429
+ sys.exit(run_check(args.config))
430
+
422
431
  elif args.command == 'desktop':
423
432
  from quantum.runtime.ui_desktop import run_desktop
424
433
  sys.exit(run_desktop(args.config, args.path, width=args.width, height=args.height))
@@ -698,6 +698,8 @@ class QueryNode(QuantumNode):
698
698
  self.interval = None
699
699
  self.paginate = False # Enable automatic pagination
700
700
  self.page = None
701
+ self.page_expr = None # DB-9: page as an expression (default {query.page})
702
+ self.sortable = False # UI-13: ORDER BY the URL's ?sort=/&dir=
701
703
  self.page_size = 20
702
704
  self.timeout = None
703
705
  self.maxrows = None
@@ -1137,6 +1139,10 @@ class ActionNode(QuantumNode):
1137
1139
  self.method = method.upper()
1138
1140
  self.params: List[QuantumParam] = []
1139
1141
  self.body: List[QuantumNode] = []
1142
+ # UI-10 (M17): params from a table's schema — see quantum/runtime/table_params.py
1143
+ self.table: Optional[str] = None
1144
+ self.table_datasource: Optional[str] = None
1145
+ self.table_columns: Optional[List[str]] = None
1140
1146
 
1141
1147
  # Action configuration
1142
1148
  self.validate_csrf = True # Auto CSRF protection
@@ -319,6 +319,9 @@ class UIFormNode(QuantumNode, UILayoutMixin):
319
319
 
320
320
  def __init__(self):
321
321
  self.on_submit: Optional[str] = None
322
+ self.rules: Optional[str] = None # UI-9: 'off' = no rules from the action
323
+ self.values: Optional[str] = None # UI-10: {row} whose fields fill the form
324
+ self.submit: Optional[str] = None # UI-10: text of the generated button
322
325
  self.children: List[QuantumNode] = []
323
326
 
324
327
  # Validation mode: 'client', 'server', or 'both' (default)
@@ -550,6 +553,8 @@ class UIInputNode(QuantumNode, UILayoutMixin):
550
553
  def __init__(self):
551
554
  self.bind: Optional[str] = None
552
555
  self.value: Optional[str] = None # initial value (UI-6)
556
+ self.search: Optional[str] = None # UI-12: id of the region it refreshes as you type
557
+ self.delay: Optional[int] = None # UI-12: ms after the last key (300 with search=)
553
558
  self.placeholder: Optional[str] = None
554
559
  self.input_type: str = "text" # text, password, email, number
555
560
  self.on_change: Optional[str] = None
@@ -673,6 +678,9 @@ class UITableNode(QuantumNode, UILayoutMixin):
673
678
  def __init__(self):
674
679
  self.source: Optional[str] = None
675
680
  self.as_var: Optional[str] = None # row variable for column content (UI-5)
681
+ self.sort: bool = False # UI-13: headers sort the query
682
+ self.edit: Optional[str] = None # UI-13: table whose cells are edited in place
683
+ self.edit_datasource: Optional[str] = None
676
684
  self.children: List[QuantumNode] = [] # UIColumnNode children
677
685
  self._init_layout_attrs()
678
686
 
@@ -697,6 +705,7 @@ class UIColumnNode(QuantumNode):
697
705
  self.label: Optional[str] = None
698
706
  self.column_width: Optional[str] = None
699
707
  self.align: Optional[str] = None # left, center, right
708
+ self.edit: bool = True # UI-13: edit="false" keeps it read-only
700
709
  # UI-5: a column with content draws it once per row (buttons, links).
701
710
  self.children: List[QuantumNode] = []
702
711
 
@@ -1870,6 +1879,23 @@ class UIDatePickerNode(QuantumNode, UILayoutMixin):
1870
1879
  return self._validate_layout()
1871
1880
 
1872
1881
 
1882
+ class UIPagerNode(QuantumNode, UILayoutMixin):
1883
+ """<ui:pager for="query"> — previous, page numbers and next over a paginated query (UI-11)."""
1884
+
1885
+ def __init__(self):
1886
+ self.for_query: Optional[str] = None
1887
+ self.param: str = 'page'
1888
+ self.window: int = 2
1889
+ self.children: List[QuantumNode] = []
1890
+ self._init_layout_attrs()
1891
+
1892
+ def to_dict(self) -> Dict[str, Any]:
1893
+ return {"type": "ui_pager", "for": self.for_query, "param": self.param}
1894
+
1895
+ def validate(self) -> List[str]:
1896
+ return [] if self.for_query else ["ui:pager needs for="]
1897
+
1898
+
1873
1899
  def is_ui_node(node) -> bool:
1874
1900
  """A ui:* element (UI-1): markup, drawn by a renderer, never executed."""
1875
1901
  return type(node).__module__ == __name__
@@ -1882,6 +1908,6 @@ CORE_TAGS = frozenset({
1882
1908
  'window', 'hbox', 'vbox', 'grid', 'panel', 'section', 'scrollbox', 'spacer', 'rule',
1883
1909
  'header', 'footer', 'card', 'card-header', 'card-body', 'card-footer', 'tabpanel', 'tab',
1884
1910
  'text', 'badge', 'alert', 'link', 'image', 'progress',
1885
- 'table', 'column', 'list', 'item',
1911
+ 'table', 'column', 'list', 'item', 'pager',
1886
1912
  'form', 'formitem', 'input', 'checkbox', 'switch', 'radio', 'select', 'option', 'button',
1887
1913
  })
@@ -28,7 +28,7 @@ from .ast_nodes import (
28
28
  UIToastNode, UIToastContainerNode,
29
29
  UICarouselNode, UISlideNode,
30
30
  UIStepperNode, UIStepNode,
31
- UICalendarNode, UIDatePickerNode,
31
+ UICalendarNode, UIDatePickerNode, UIPagerNode,
32
32
  )
33
33
  from quantum.core.features.theming.src import UIThemeNode, UIColorNode
34
34
  from quantum.core.ast_nodes import TextNode
@@ -81,6 +81,7 @@ class UIParser:
81
81
  'select': '_parse_ui_select',
82
82
  'table': '_parse_ui_table',
83
83
  'column': '_parse_ui_column',
84
+ 'pager': '_parse_ui_pager',
84
85
  'list': '_parse_ui_list',
85
86
  'item': '_parse_ui_item',
86
87
  'image': '_parse_ui_image',
@@ -137,9 +138,24 @@ class UIParser:
137
138
  import difflib
138
139
  parecidas = difflib.get_close_matches(local_name, self.UI_TAG_MAP, n=1)
139
140
  dica = f"; did you mean <ui:{parecidas[0]}>?" if parecidas else ""
140
- raise UIParseError(f"Unknown UI tag: <ui:{local_name}>{dica}")
141
+ erro = UIParseError(f"Unknown UI tag: <ui:{local_name}>{dica}")
142
+ erro.line = getattr(element, 'sourceline', None)
143
+ raise erro
141
144
  method = getattr(self, method_name)
142
- return method(element)
145
+ try:
146
+ node = method(element)
147
+ except UIParseError as exc:
148
+ if getattr(exc, 'line', None) is None:
149
+ exc.line = getattr(element, 'sourceline', None)
150
+ raise
151
+ # DEV-2: a ui:* element knows its line and file, like a q: statement.
152
+ if getattr(element, 'sourceline', None) and node is not None:
153
+ try:
154
+ node.source_line = element.sourceline
155
+ node.source_file = getattr(self.parent, '_source_path', None)
156
+ except AttributeError:
157
+ pass
158
+ return node
143
159
 
144
160
  # ------------------------------------------------------------------
145
161
  # Helpers
@@ -344,6 +360,13 @@ class UIParser:
344
360
  node = UIFormNode()
345
361
  node.on_submit = element.get('on-submit')
346
362
  node.send = element.get('with')
363
+ # UI-9: rules="off" stops the form from taking its fields' rules from the action.
364
+ node.rules = element.get('rules')
365
+ # UI-10: an edit form opens with a row's values; the generated button's text.
366
+ node.values = element.get('values')
367
+ node.submit = element.get('submit')
368
+ if node.rules not in (None, 'on', 'off'):
369
+ raise UIParseError(f'<ui:form rules="{node.rules}">: use on (the default) or off')
347
370
 
348
371
  # Validation attributes
349
372
  node.validation_mode = element.get('validation', 'both')
@@ -415,6 +438,14 @@ class UIParser:
415
438
  node = UIInputNode()
416
439
  node.bind = element.get('bind')
417
440
  node.value = element.get('value')
441
+ # UI-12: search as you type — refresh the element with this id.
442
+ node.search = element.get('search')
443
+ espera = element.get('delay')
444
+ if espera is not None and not espera.isdigit():
445
+ raise UIParseError(f'<ui:input delay="{espera}">: milliseconds, e.g. delay="300"')
446
+ node.delay = int(espera) if espera is not None else (300 if node.search else None)
447
+ if node.search is not None and not node.bind:
448
+ raise UIParseError('<ui:input search=...> needs bind= (the URL parameter it sets)')
418
449
  node.placeholder = element.get('placeholder')
419
450
  node.input_type = element.get('type', 'text')
420
451
  node.on_change = element.get('on-change')
@@ -483,6 +514,14 @@ class UIParser:
483
514
  node = UITableNode()
484
515
  node.source = element.get('source')
485
516
  node.as_var = element.get('as')
517
+ # UI-13: sortable headers and cells edited in place.
518
+ node.sort = self._parse_bool(element.get('sort'))
519
+ node.edit = element.get('edit')
520
+ node.edit_datasource = element.get('datasource')
521
+ if node.edit and not node.edit_datasource:
522
+ raise UIParseError(f'<ui:table edit="{node.edit}"> needs datasource= (where the table is)')
523
+ if (node.sort or node.edit) and not node.source:
524
+ raise UIParseError('<ui:table sort/edit> needs source="{query}"')
486
525
  self._apply_layout_attrs(node, element)
487
526
  # Parse child <ui:column> elements
488
527
  self._parse_children(element, node)
@@ -494,9 +533,23 @@ class UIParser:
494
533
  node.label = element.get('label')
495
534
  node.column_width = element.get('width')
496
535
  node.align = element.get('align')
536
+ node.edit = element.get('edit', 'true') != 'false'
497
537
  self._parse_children(element, node)
498
538
  return node
499
539
 
540
+ def _parse_ui_pager(self, element: ET.Element) -> UIPagerNode:
541
+ node = UIPagerNode()
542
+ node.for_query = element.get('for')
543
+ if not node.for_query:
544
+ raise UIParseError('<ui:pager> needs for="<the paginated q:query>"')
545
+ node.param = element.get('param') or 'page'
546
+ janela = element.get('window')
547
+ if janela is not None and not janela.isdigit():
548
+ raise UIParseError(f'<ui:pager window="{janela}">: the number of pages shown each side of the current one')
549
+ node.window = int(janela) if janela is not None else 2
550
+ self._apply_layout_attrs(node, element)
551
+ return node
552
+
500
553
  def _parse_ui_list(self, element: ET.Element) -> UIListNode:
501
554
  node = UIListNode()
502
555
  node.source = element.get('source')
@@ -11,6 +11,7 @@ from typing import Union, Optional, List
11
11
  # Fix imports
12
12
 
13
13
  from quantum.core.html_compat import normalise_html
14
+ from quantum.core.xml_lines import fromstring_with_lines, line_of
14
15
  from quantum.core.ast_nodes import (
15
16
  QuantumNode, ComponentNode, ApplicationNode, JobNode,
16
17
  QuantumParam, QuantumReturn, QuantumRoute,
@@ -222,6 +223,23 @@ def _explain_xml_error(error, source: str) -> str:
222
223
  return message
223
224
 
224
225
 
226
+ def _at(erro: 'QuantumParseError', onde) -> 'QuantumParseError':
227
+ """The error belongs to this node's (or element's) line."""
228
+ erro.line = line_of(onde)
229
+ return erro
230
+
231
+
232
+ def _with_line(erro: 'QuantumParseError', source: str, line: Optional[int] = None) -> 'QuantumParseError':
233
+ """DEV-2: a parse error says where it is — `at line N: <the line>`."""
234
+ line = line or getattr(erro, 'line', None)
235
+ erro.line = line
236
+ if line and ' at line ' not in str(erro):
237
+ linhas = source.splitlines()
238
+ trecho = linhas[line - 1].strip() if 0 < line <= len(linhas) else ''
239
+ erro.args = (f"{erro}\n at line {line}: {trecho}" if trecho else f"{erro} (line {line})",)
240
+ return erro
241
+
242
+
225
243
  class QuantumParser:
226
244
  """Main parser for .q files"""
227
245
 
@@ -370,17 +388,19 @@ class QuantumParser:
370
388
  # parse for exactly those two reasons and returned HTTP 400.
371
389
  content = normalise_html(source)
372
390
  content = self._inject_namespace(content)
373
- root = ET.fromstring(content)
391
+ root = fromstring_with_lines(content)
374
392
  self._nest_else_siblings(root)
375
393
  return self._parse_root_element(root, Path("<string>"))
376
394
  except ET.ParseError as e:
377
- raise QuantumParseError(_explain_xml_error(e, source))
378
- except QuantumParseError:
379
- raise
395
+ erro = QuantumParseError(_explain_xml_error(e, source))
396
+ erro.line = getattr(e, 'position', (None, None))[0]
397
+ raise erro
398
+ except QuantumParseError as e:
399
+ raise _with_line(e, source)
380
400
  except ParserError as e:
381
401
  # A tag parser's own error is a parse error with a message meant
382
402
  # for the author, not an "Unexpected error".
383
- raise QuantumParseError(str(e)) from e
403
+ raise _with_line(QuantumParseError(str(e)), source, getattr(e, 'line', None)) from e
384
404
  except Exception as e:
385
405
  raise QuantumParseError(f"Unexpected error: {e}")
386
406
 
@@ -412,7 +432,9 @@ class QuantumParser:
412
432
  self._source_path = str(path.resolve())
413
433
  source = Path(file_path).read_text(encoding="utf-8")
414
434
  return self.parse(source)
415
- except QuantumParseError:
435
+ except QuantumParseError as e:
436
+ if getattr(e, 'file', None) is None:
437
+ e.file = str(path.resolve()) # DEV-2
416
438
  raise
417
439
  except ParserError as e:
418
440
  # A tag parser's own error is a parse error with a message meant
@@ -498,7 +520,7 @@ class QuantumParser:
498
520
  conhecidas |= {'return', 'else', 'elseif'}
499
521
  parecidas = difflib.get_close_matches(name, sorted(conhecidas), n=3, cutoff=0.6)
500
522
  dica = (" Did you mean " + " or ".join(f"<q:{p}>" for p in parecidas) + "?") if parecidas else ""
501
- return f"<q:{name}> is not a Quantum tag.{dica}"
523
+ return f"<q:{name}> is not a Quantum tag (PARSE-1).{dica}"
502
524
 
503
525
  @staticmethod
504
526
  def _is_quantum_tag(element: ET.Element) -> bool:
@@ -683,12 +705,12 @@ class QuantumParser:
683
705
  lidos = names_read(re.sub(r'\{([^{}]*)\}', r'\1', condicao)) & definidos
684
706
  if lidos:
685
707
  nome = sorted(lidos)[0]
686
- raise QuantumParseError(
708
+ raise _at(QuantumParseError(
687
709
  f'<q:if condition="{condicao}"> in <q:component name="{component.name}"> '
688
- f'redirects, so it also runs before the page\'s q:action — where the page '
710
+ f'redirects (AUTH-6), so it also runs before the page\'s q:action — where the page '
689
711
  f'has not run and {{{nome}}} does not exist, and the guard would let the '
690
712
  f'action through. Read the scope directly (e.g. session.x), or protect the '
691
- f'page with require_auth / require_role. See {HOW_A_PAGE_RUNS}')
713
+ f'page with require_auth / require_role. See {HOW_A_PAGE_RUNS}'), guarda)
692
714
 
693
715
  # Nodes the HTML renderer draws. Anything else inside markup is a statement,
694
716
  # and statements run in the execution pass, before the render pass sees
@@ -762,21 +784,21 @@ class QuantumParser:
762
784
  for node in nodes:
763
785
  if not isinstance(node, self._RENDERED) and not is_ui_node(node):
764
786
  if onde and onde[0] == 'markup':
765
- raise QuantumParseError(
787
+ raise _at(QuantumParseError(
766
788
  f'{tag(node)} inside {onde[1]} in <q:component name="{component.name}"> '
767
- f'never runs: statements run before the page is rendered, and markup '
789
+ f'never runs (PARSE-2): statements run before the page is rendered, and markup '
768
790
  f'is only rendered. Move it above the markup (or into a q:action). '
769
- f'See {HOW_A_PAGE_RUNS}')
791
+ f'See {HOW_A_PAGE_RUNS}'), node)
770
792
  nome = getattr(node, 'name', None)
771
793
  # A running total read AFTER the loop is fine; a value the
772
794
  # loop's own rows read is not.
773
795
  if onde and isinstance(nome, str) and usado(nome, onde[1]):
774
- raise QuantumParseError(
796
+ raise _at(QuantumParseError(
775
797
  f'{tag(node)} inside a q:loop whose rows use {{{nome}}} in '
776
798
  f'<q:component name="{component.name}">: the loop runs for every item '
777
- f'before any row is rendered, so every row would show the last value. '
799
+ f'before any row is rendered, so every row would show the last value (PARSE-2). '
778
800
  f'Compute it in the expression instead, e.g. {{item.price * item.qty}}. '
779
- f'See {HOW_A_PAGE_RUNS}')
801
+ f'See {HOW_A_PAGE_RUNS}'), node)
780
802
  continue
781
803
  if isinstance(node, HTMLNode):
782
804
  andar(filhos(node), onde if onde and onde[0] == 'markup' else ('markup', f'<{node.tag}>'))
@@ -844,6 +866,27 @@ class QuantumParser:
844
866
  if_node.add_else_statement(stmt)
845
867
 
846
868
  def _parse_statement(self, element: ET.Element) -> Optional[QuantumNode]:
869
+ """Parse one statement, and remember where it is in the file (DEV-2).
870
+
871
+ The node gets `source_line`, which runtime errors report; a parse
872
+ error from this element — or from inside it, if nothing deeper
873
+ claimed it — gets `line`.
874
+ """
875
+ try:
876
+ node = self._parse_statement_node(element)
877
+ except (QuantumParseError, ParserError) as exc:
878
+ if getattr(exc, 'line', None) is None:
879
+ exc.line = getattr(element, 'sourceline', None)
880
+ raise
881
+ if node is not None and getattr(element, 'sourceline', None):
882
+ try:
883
+ node.source_line = element.sourceline
884
+ node.source_file = self._source_path
885
+ except AttributeError:
886
+ pass # a node with __slots__
887
+ return node
888
+
889
+ def _parse_statement_node(self, element: ET.Element) -> Optional[QuantumNode]:
847
890
  """Parse individual statement (return, set, dispatchEvent, etc)"""
848
891
  element_type = self._get_element_name(element)
849
892
 
@@ -857,7 +900,9 @@ class QuantumParser:
857
900
  try:
858
901
  return UIParser(self, page_mode=True).parse_ui_element(element_type, element)
859
902
  except UIParseError as exc:
860
- raise QuantumParseError(str(exc)) from exc
903
+ erro = QuantumParseError(str(exc))
904
+ erro.line = getattr(exc, 'line', None) # the inner ui:* element's line
905
+ raise erro from exc
861
906
 
862
907
  # PARSE-1: <q:html>, <q:div> — an HTML element name in the q:
863
908
  # namespace. The registry answered for it with the HTML parser, so
@@ -77,7 +77,27 @@ class QueryParser(BaseTagParser):
77
77
  # Parse attributes
78
78
  query_node.source = source
79
79
  query_node.paginate = self.get_bool_attr(element, 'paginate', False)
80
- query_node.page = self.get_int_attr(element, 'page', 0) or None
80
+ # UI-13: ordered by the URL's ?sort=/&dir= (a <ui:table sort="true"> writes them)
81
+ query_node.sortable = self.get_bool_attr(element, 'sortable', False)
82
+ # DB-9: page= is an expression, resolved when the query runs; it was
83
+ # read as an int at parse time, so page="{query.page}" was always
84
+ # page 1. With paginate="true" and no page=, the page is the URL's
85
+ # `page` parameter — the one <ui:pager> writes.
86
+ pagina = element.get('page')
87
+ query_node.page = None
88
+ query_node.page_expr = None
89
+ if pagina is not None:
90
+ if '{' in pagina:
91
+ query_node.page_expr = pagina
92
+ elif pagina.strip().isdigit():
93
+ query_node.page = int(pagina)
94
+ else:
95
+ from quantum.core.parser import QuantumParseError
96
+ raise QuantumParseError(
97
+ f'<q:query name="{name}" page="{pagina}">: a page number or an expression, '
98
+ f'e.g. page="{{query.pagina}}"')
99
+ elif query_node.paginate:
100
+ query_node.page_expr = '{query.page}'
81
101
  # Accept both spellings: the codebase convention is camelCase, but
82
102
  # snake_case reads naturally in XML and the examples used it —
83
103
  # the mismatch made page_size None and crashed pagination with