tina4-python 3.13.89__tar.gz → 3.13.90__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 (172) hide show
  1. {tina4_python-3.13.89 → tina4_python-3.13.90}/PKG-INFO +1 -1
  2. {tina4_python-3.13.89 → tina4_python-3.13.90}/pyproject.toml +1 -1
  3. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/cli/__init__.py +5 -3
  4. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/js/tina4-dev-admin.min.js +2 -2
  5. {tina4_python-3.13.89 → tina4_python-3.13.90}/.gitignore +0 -0
  6. {tina4_python-3.13.89 → tina4_python-3.13.90}/README.md +0 -0
  7. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/CLAUDE.md +0 -0
  8. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/HtmlElement.py +0 -0
  9. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/Testing.py +0 -0
  10. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/__init__.py +0 -0
  11. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/ai/__init__.py +0 -0
  12. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/api/__init__.py +0 -0
  13. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/auth/__init__.py +0 -0
  14. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/cache/__init__.py +0 -0
  15. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/container/__init__.py +0 -0
  16. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/context/__init__.py +0 -0
  17. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/context/chunker.py +0 -0
  18. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/core/__init__.py +0 -0
  19. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/core/cache.py +0 -0
  20. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/core/constants.py +0 -0
  21. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/core/events.py +0 -0
  22. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/core/middleware.py +0 -0
  23. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/core/rate_limiter.py +0 -0
  24. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/core/request.py +0 -0
  25. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/core/response.py +0 -0
  26. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/core/router.py +0 -0
  27. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/core/server.py +0 -0
  28. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/crud/__init__.py +0 -0
  29. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/database/__init__.py +0 -0
  30. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/database/adapter.py +0 -0
  31. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/database/connection.py +0 -0
  32. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/database/firebird.py +0 -0
  33. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/database/mongodb.py +0 -0
  34. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/database/mssql.py +0 -0
  35. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/database/mysql.py +0 -0
  36. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/database/odbc.py +0 -0
  37. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/database/postgres.py +0 -0
  38. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/database/sqlite.py +0 -0
  39. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/debug/__init__.py +0 -0
  40. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/debug/error_overlay.py +0 -0
  41. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/dev_admin/__init__.py +0 -0
  42. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/dev_admin/metrics.py +0 -0
  43. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/dev_admin/plan.py +0 -0
  44. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/dev_admin/project_index.py +0 -0
  45. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/docs.py +0 -0
  46. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/docstore/__init__.py +0 -0
  47. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/dotenv/__init__.py +0 -0
  48. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/env.py +0 -0
  49. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/frond/FROND.md +0 -0
  50. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/frond/__init__.py +0 -0
  51. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/frond/compiler.py +0 -0
  52. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/frond/engine.py +0 -0
  53. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/frond/parser.py +0 -0
  54. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/auth/meta.json +0 -0
  55. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  56. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/database/meta.json +0 -0
  57. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  58. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/error-overlay/meta.json +0 -0
  59. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  60. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/orm/meta.json +0 -0
  61. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  62. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  63. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/queue/meta.json +0 -0
  64. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  65. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/rest-api/meta.json +0 -0
  66. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  67. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/templates/meta.json +0 -0
  68. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  69. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  70. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/graphql/__init__.py +0 -0
  71. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/i18n/__init__.py +0 -0
  72. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/mcp/__init__.py +0 -0
  73. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/mcp/protocol.py +0 -0
  74. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/mcp/tools.py +0 -0
  75. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/messenger/__init__.py +0 -0
  76. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/migration/__init__.py +0 -0
  77. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/migration/runner.py +0 -0
  78. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/mqtt/__init__.py +0 -0
  79. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/mqtt/message.py +0 -0
  80. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/orm/__init__.py +0 -0
  81. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/orm/fields.py +0 -0
  82. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/orm/model.py +0 -0
  83. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/__feedback/widget.js +0 -0
  84. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/css/tina4.css +0 -0
  85. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/css/tina4.min.css +0 -0
  86. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/favicon.ico +0 -0
  87. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/images/logo.svg +0 -0
  88. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  89. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/js/frond.js +0 -0
  90. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/js/frond.min.js +0 -0
  91. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/js/tina4.min.js +0 -0
  92. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/js/tina4js.min.js +0 -0
  93. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/swagger/index.html +0 -0
  94. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  95. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/query_builder/__init__.py +0 -0
  96. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/queue/__init__.py +0 -0
  97. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/queue/job.py +0 -0
  98. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/queue/kafka_backend.py +0 -0
  99. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/queue/lite_backend.py +0 -0
  100. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/queue/mongo_backend.py +0 -0
  101. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/queue/rabbitmq_backend.py +0 -0
  102. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/queue_backends/__init__.py +0 -0
  103. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/queue_backends/kafka_backend.py +0 -0
  104. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/queue_backends/mongo_backend.py +0 -0
  105. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  106. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/realtime/__init__.py +0 -0
  107. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/realtime/models/Attachment.py +0 -0
  108. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/realtime/models/Channel.py +0 -0
  109. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/realtime/models/ChannelMember.py +0 -0
  110. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/realtime/models/Message.py +0 -0
  111. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/realtime/models/Workspace.py +0 -0
  112. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/realtime/models/__init__.py +0 -0
  113. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/realtime/storage.py +0 -0
  114. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/__init__.py +0 -0
  115. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  116. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_badges.scss +0 -0
  117. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  118. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_cards.scss +0 -0
  119. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_forms.scss +0 -0
  120. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_grid.scss +0 -0
  121. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_modals.scss +0 -0
  122. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_nav.scss +0 -0
  123. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_pagination.scss +0 -0
  124. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_reset.scss +0 -0
  125. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_tables.scss +0 -0
  126. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_typography.scss +0 -0
  127. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  128. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/_variables.scss +0 -0
  129. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/base.scss +0 -0
  130. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/colors.scss +0 -0
  131. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/scss/tina4css/tina4.scss +0 -0
  132. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/seeder/__init__.py +0 -0
  133. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/service/__init__.py +0 -0
  134. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/session/__init__.py +0 -0
  135. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/session_handlers/__init__.py +0 -0
  136. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  137. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/session_handlers/redis_handler.py +0 -0
  138. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/session_handlers/valkey_handler.py +0 -0
  139. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/swagger/__init__.py +0 -0
  140. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/components/crud.twig +0 -0
  141. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  142. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  143. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/docker/python/Dockerfile +0 -0
  144. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  145. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/errors/302.twig +0 -0
  146. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/errors/401.twig +0 -0
  147. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/errors/403.twig +0 -0
  148. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/errors/404.twig +0 -0
  149. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/errors/500.twig +0 -0
  150. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/errors/502.twig +0 -0
  151. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/errors/503.twig +0 -0
  152. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/errors/base.twig +0 -0
  153. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/frontend/README.md +0 -0
  154. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/templates/readme.md +0 -0
  155. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/test/__init__.py +0 -0
  156. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/test_client/__init__.py +0 -0
  157. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  158. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  159. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  160. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  161. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  162. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  163. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  164. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  165. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  166. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  167. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  168. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  169. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/validator/__init__.py +0 -0
  170. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/websocket/__init__.py +0 -0
  171. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/websocket/backplane.py +0 -0
  172. {tina4_python-3.13.89 → tina4_python-3.13.90}/tina4_python/wsdl/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tina4-python
3
- Version: 3.13.89
3
+ Version: 3.13.90
4
4
  Summary: Tina4 Python v3 — Zero-dependency, lightweight web framework
5
5
  Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tina4-python"
3
- version = "3.13.89"
3
+ version = "3.13.90"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -1766,7 +1766,9 @@ def _sample_literal(field_type: str) -> str:
1766
1766
 
1767
1767
  def _emit_model_test(model: str, table: str, fields: list) -> None:
1768
1768
  """model → real SQLite roundtrip (create / read back / missing → None)."""
1769
- fields = fields or [("name", "string")]
1769
+ # Reuse the single DEFAULT_FIELDS constant rather than re-stating the literal,
1770
+ # so the co-emitted test can never describe a shape the model does not have.
1771
+ fields = fields or list(DEFAULT_FIELDS)
1770
1772
  payload = ", ".join(f'"{fname}": {_sample_literal(ftype)}' for fname, ftype in fields)
1771
1773
  # Assert a STRING field round-trips (type-safe); else just the id round-trips
1772
1774
  # (avoids datetime/bool/float equality pitfalls on the read-back).
@@ -2615,7 +2617,7 @@ def _gen_form(name: str, flags: dict = None):
2615
2617
  tina4python generate form Product --fields "name:string,price:float"
2616
2618
  """
2617
2619
  flags = flags or {}
2618
- fields = _parse_fields(flags.get("fields", ""))
2620
+ fields = _fields_or_default(flags.get("fields", ""))
2619
2621
  table = _to_table(name)
2620
2622
  route_name = table + "s"
2621
2623
 
@@ -2637,7 +2639,7 @@ def _gen_form(name: str, flags: dict = None):
2637
2639
 
2638
2640
  # Build form fields
2639
2641
  field_html = ""
2640
- for fname, ftype in (fields or [("name", "string")]):
2642
+ for fname, ftype in fields:
2641
2643
  itype = input_types.get(ftype, "text")
2642
2644
  label = fname.replace("_", " ").title()
2643
2645
  step = ' step="0.01"' if ftype in ("float", "numeric", "decimal") else ""
@@ -985,7 +985,7 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
985
985
  <span class="thread-pip"></span>
986
986
  <span class="thread-title">${g(t.title)}</span>
987
987
  ${o}
988
- </div>`}).join("")}let cd=!1;async function Qa(i=0){for(let e=0;;e++)try{Se=await NZ(),cd=!1,EQ();return}catch(t){if(e>=i){cd=!0,console.error("refreshThreadList failed",t);return}await new Promise(n=>setTimeout(n,250*2**e))}}function HZ(i){const e=document.getElementById("editor-ai-messages");if(!e)return;const t=rt.get(i)||[];if(!t.length){e.innerHTML='<div class="ai-msg ai-bot" style="opacity:0.6">Start the conversation…</div>';return}e.innerHTML="";for(const n of t){const r=document.createElement("div");r.className=`ai-msg ai-${n.role==="user"?"user":"bot"}`,n.role==="user"?r.textContent=n.content:r.innerHTML=fs(n.content),e.appendChild(r)}e.scrollTop=e.scrollHeight}async function AQ(i,e=!1){if(!(!e&&rt.has(i)))try{const t=await FZ(i);rt.set(i,t)}catch(t){console.error(`loadThreadMessages(${i}) failed`,t),rt.set(i,[])}}async function dd(i){if(i!==ve){ve=i;try{localStorage.setItem(WQ,i)}catch{}EQ(),await AQ(i),HZ(i)}}async function KZ(){try{const i=await cs();Se.push(i),rt.set(i.id,[]),await dd(i.id);const e=document.getElementById("editor-ai-input");e==null||e.focus()}catch(i){AZ(`<span style="color:var(--danger)">Couldn't create thread: ${g(String((i==null?void 0:i.message)||i))}</span>`,"bot")}}async function JZ(i){const e=document.querySelector(`.thread-row[data-thread-id="${CSS.escape(i)}"]`),t=e==null?void 0:e.querySelector(".thread-title");if(!e||!t)return;const n=t.textContent||"",r=document.createElement("input");r.className="thread-title-edit",r.value=n,t.replaceWith(r),r.focus(),r.select();const s=a=>{const l=document.createElement("span");l.className="thread-title",l.textContent=a,r.replaceWith(l)},o=async()=>{const a=r.value.trim();if(!a||a===n){s(n);return}try{const l=await ga(i,{title:a}),O=Se.find(c=>c.id===i);O&&(O.title=l.title),s(l.title)}catch(l){console.error("rename failed",l),s(n)}};r.addEventListener("blur",()=>{o()}),r.addEventListener("keydown",a=>{a.key==="Enter"?(a.preventDefault(),r.blur()):a.key==="Escape"&&(a.preventDefault(),s(n))})}async function ez(){if(await Qa(4),!Se.length){ve=null,er();return}try{const i=localStorage.getItem(WQ);i&&Se.some(e=>e.id===i)&&(ve=i)}catch{}ya()}window.__editorThreadNew=()=>{KZ()},window.__editorThreadSwitch=i=>{dd(i)},window.__editorThreadRename=i=>{JZ(i)};const tz={done:"DONE",awaiting_customer:"AWAITING YOU",wont_do:"WONT DO",blocked:"BLOCKED",feedback:"NEW FEEDBACK",idle:"IDLE",running:"RUNNING"};function hd(i){const e=tz[i]||i.toUpperCase();return`<span class="status-pill" data-status="${g(i)}">${g(e)}</span>`}function fd(i){if(!i)return"";let e;if(/^\d+Z$/.test(i)?e=new Date(parseInt(i,10)*1e3):e=new Date(i),isNaN(e.getTime()))return i;const t=n=>String(n).padStart(2,"0");return`${t(e.getDate())}/${t(e.getMonth()+1)}/${e.getFullYear()} ${t(e.getHours())}:${t(e.getMinutes())}`}let Jn=null;function ya(){const i=e=>document.getElementById(e);i("threads-pane-head-list").hidden=!1,i("threads-pane-head-detail").hidden=!0,i("threads-list-view").hidden=!1,i("threads-detail-view").hidden=!0,Qa().then(er)}async function ba(i){await dd(i);const e=Se.find(s=>s.id===i);if(!e)return;const t=s=>document.getElementById(s);t("threads-pane-head-list").hidden=!0,t("threads-pane-head-detail").hidden=!1,t("threads-list-view").hidden=!0,t("threads-detail-view").hidden=!1,t("threads-detail-title").textContent=e.title||"Thread";const n=t("threads-detail-meta"),r=e.sender?`<span>📨 from ${g(e.sender)}</span>`:"";n.innerHTML=`${hd(e.status_hint||"idle")} <span>${g(fd(e.last_message_at))}</span> ${r}`,pd(i),setTimeout(()=>{var s;return(s=t("threads-reply-input"))==null?void 0:s.focus()},30)}async function iz(){if(!ve)return;const i=ve;try{await ga(i,{archived:!0,closure_reason:"done"});const e=Se.find(t=>t.id===i);e&&(e.archived=!0,e.closure_reason="done"),ve=null,ya()}catch(e){console.error("archive failed",e)}}async function nz(i){try{await ga(i,{archived:!0});const e=Se.find(t=>t.id===i);e&&(e.archived=!0),ve===i?(ve=null,ya()):er()}catch(e){console.error("archive-from-list failed",e)}}function er(){const i=document.getElementById("threads-rows");if(!i)return;if(!Se.length){i.innerHTML=cd?`<div class="threads-empty">
988
+ </div>`}).join("")}let cd=!1;async function Qa(i=0){for(let e=0;;e++)try{Se=await NZ(),cd=!1,EQ();return}catch(t){if(e>=i){cd=!0,console.error("refreshThreadList failed",t);return}await new Promise(n=>setTimeout(n,250*2**e))}}function HZ(i){const e=document.getElementById("editor-ai-messages");if(!e)return;const t=rt.get(i)||[];if(!t.length){e.innerHTML='<div class="ai-msg ai-bot" style="opacity:0.6">Start the conversation…</div>';return}e.innerHTML="";for(const n of t){const r=document.createElement("div");r.className=`ai-msg ai-${n.role==="user"?"user":"bot"}`,n.role==="user"?r.textContent=n.content:r.innerHTML=fs(n.content),e.appendChild(r)}e.scrollTop=e.scrollHeight}async function AQ(i,e=!1){if(!(!e&&rt.has(i)))try{const t=await FZ(i);rt.set(i,t)}catch(t){console.error(`loadThreadMessages(${i}) failed`,t),rt.set(i,[])}}async function dd(i){if(i!==ve){ve=i;try{localStorage.setItem(WQ,i)}catch{}EQ(),await AQ(i),HZ(i)}}async function KZ(){try{const i=await cs();Se.push(i),rt.set(i.id,[]),await dd(i.id);const e=document.getElementById("editor-ai-input");e==null||e.focus()}catch(i){AZ(`<span style="color:var(--danger)">Couldn't create thread: ${g(String((i==null?void 0:i.message)||i))}</span>`,"bot")}}async function JZ(i){const e=document.querySelector(`.thread-row[data-thread-id="${CSS.escape(i)}"]`),t=e==null?void 0:e.querySelector(".thread-title");if(!e||!t)return;const n=t.textContent||"",r=document.createElement("input");r.className="thread-title-edit",r.value=n,t.replaceWith(r),r.focus(),r.select();const s=a=>{const l=document.createElement("span");l.className="thread-title",l.textContent=a,r.replaceWith(l)},o=async()=>{const a=r.value.trim();if(!a||a===n){s(n);return}try{const l=await ga(i,{title:a}),O=Se.find(c=>c.id===i);O&&(O.title=l.title),s(l.title)}catch(l){console.error("rename failed",l),s(n)}};r.addEventListener("blur",()=>{o()}),r.addEventListener("keydown",a=>{a.key==="Enter"?(a.preventDefault(),r.blur()):a.key==="Escape"&&(a.preventDefault(),s(n))})}async function ez(){if(await Qa(4),!Se.length){ve=null,er();return}try{const i=localStorage.getItem(WQ);i&&Se.some(e=>e.id===i)&&(ve=i)}catch{}ya()}window.__editorThreadNew=()=>{KZ()},window.__editorThreadSwitch=i=>{dd(i)},window.__editorThreadRename=i=>{JZ(i)};const tz={done:"DONE",awaiting_customer:"AWAITING YOU",wont_do:"WONT DO",blocked:"BLOCKED",feedback:"NEW FEEDBACK",idle:"IDLE",running:"RUNNING"};function hd(i){const e=tz[i]||i.toUpperCase();return`<span class="status-pill" data-status="${g(i)}">${g(e)}</span>`}function fd(i){if(!i)return"";let e;if(/^\d+Z$/.test(i)?e=new Date(parseInt(i,10)*1e3):e=new Date(i),isNaN(e.getTime()))return i;const t=n=>String(n).padStart(2,"0");return`${t(e.getDate())}/${t(e.getMonth()+1)}/${e.getFullYear()} ${t(e.getHours())}:${t(e.getMinutes())}`}let Jn=null;function ya(){const i=s=>document.getElementById(s),e=i("threads-pane-head-list");e&&(e.hidden=!1);const t=i("threads-pane-head-detail");t&&(t.hidden=!0);const n=i("threads-list-view");n&&(n.hidden=!1);const r=i("threads-detail-view");r&&(r.hidden=!0),Qa().then(er)}async function ba(i){await dd(i);const e=Se.find(c=>c.id===i);if(!e)return;const t=c=>document.getElementById(c),n=t("threads-pane-head-list");n&&(n.hidden=!0);const r=t("threads-pane-head-detail");r&&(r.hidden=!1);const s=t("threads-list-view");s&&(s.hidden=!0);const o=t("threads-detail-view");o&&(o.hidden=!1);const a=t("threads-detail-title");a&&(a.textContent=e.title||"Thread");const l=t("threads-detail-meta"),O=e.sender?`<span>📨 from ${g(e.sender)}</span>`:"";l&&(l.innerHTML=`${hd(e.status_hint||"idle")} <span>${g(fd(e.last_message_at))}</span> ${O}`),pd(i),setTimeout(()=>{var c;return(c=t("threads-reply-input"))==null?void 0:c.focus()},30)}async function iz(){if(!ve)return;const i=ve;try{await ga(i,{archived:!0,closure_reason:"done"});const e=Se.find(t=>t.id===i);e&&(e.archived=!0,e.closure_reason="done"),ve=null,ya()}catch(e){console.error("archive failed",e)}}async function nz(i){try{await ga(i,{archived:!0});const e=Se.find(t=>t.id===i);e&&(e.archived=!0),ve===i?(ve=null,ya()):er()}catch(e){console.error("archive-from-list failed",e)}}function er(){const i=document.getElementById("threads-rows");if(!i)return;if(!Se.length){i.innerHTML=cd?`<div class="threads-empty">
989
989
  <div style="margin-bottom:0.6rem">Can't reach the agent — threads unavailable.</div>
990
990
  <button type="button" class="action-pill"
991
991
  onclick="window.__threadsRetry()">Retry</button>
@@ -1012,7 +1012,7 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
1012
1012
  <span>severity: ${g(e.severity||"-")}</span>
1013
1013
  </div>
1014
1014
  <div style="margin-top:0.3rem">${g(e.summary||"")}</div>
1015
- </div>`}catch{return fs(i)}}async function az(){try{const i=await cs();Se.push(i),rt.set(i.id,[]),await ba(i.id)}catch(i){console.error("threadsNew failed",i)}}async function lz(){if(!ve)return;const i=Se.find(t=>t.id===ve);if(!i)return;const e=window.prompt("Rename thread:",i.title);if(!(e==null||e.trim()===""||e===i.title))try{const t=await ga(ve,{title:e.trim()});i.title=t.title;const n=document.getElementById("threads-detail-title");n&&(n.textContent=t.title),er()}catch(t){console.error("rename failed",t)}}function Oz(i){return i?i.status_hint==="done"||i.status_hint==="wont_do"||i.closure_reason==="done"||i.closure_reason==="wont_do":!1}async function md(){const i=document.getElementById("threads-reply-input");if(!i)return;let e=i.value.trim();if(!e)return;i.value="";const t=e.match(/^new topic:\s*(.*)$/is);if(t){const o=t[1].trim();try{const a=await cs(o.slice(0,80)||void 0);if(Se.push(a),rt.set(a.id,[]),await ba(a.id),!o)return;e=o}catch(a){console.error("New Topic spawn failed",a);return}}if(!t&&ve&&Oz(Se.find(o=>o.id===ve)))try{const o=await cs(e.slice(0,80)||void 0);Se.push(o),rt.set(o.id,[]),await ba(o.id)}catch(o){console.error("auto-new-thread on done reply failed",o);return}if(!ve)try{const o=await cs(e.slice(0,80));Se.push(o),rt.set(o.id,[]),ve=o.id}catch(o){console.error("auto-create failed",o);return}const n=ve,r=rt.get(n)||[];r.push({id:`local-${Date.now()}`,role:"user",content:e,timestamp:new Date().toISOString(),thread_id:n}),rt.set(n,r),pd(n),$a.add(n),er();const s=document.getElementById("threads-chat");Jn==null||Jn.abort(),Jn=new AbortController;try{await pz(e,n,Jn.signal,s)}catch(o){if((o==null?void 0:o.name)!=="AbortError"){const a=document.createElement("div");a.className="ai-msg ai-bot",a.style.color="var(--danger,#f38ba8)",a.textContent=`Connection failed: ${(o==null?void 0:o.message)||o}`,s==null||s.appendChild(a)}}finally{$a.delete(n),Jn=null;try{await AQ(n,!0)}catch{}await Qa(),pd(n);const o=Se.find(a=>a.id===n);if(o){const a=document.getElementById("threads-detail-meta"),l=o.sender?`<span>📨 from ${g(o.sender)}</span>`:"";a.innerHTML=`${hd(o.status_hint||"idle")} <span>${g(fd(o.last_message_at))}</span> ${l}`}}}queueMicrotask(()=>{const i=document.getElementById("threads-reply-form");i&&i.addEventListener("submit",t=>{t.preventDefault(),md()});const e=document.getElementById("threads-reply-input");e&&e.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),t.stopPropagation(),md())})}),window.__threadsShowList=()=>ya(),window.__threadsShowDetail=i=>{ba(i)},window.__threadsNew=()=>{az()},window.__threadsRetry=()=>{Qa(2).then(er)},window.__threadsRenameActive=()=>{lz()};let un=!1;async function jQ(){const i=document.getElementById("plans-panel"),e=document.getElementById("plans-toggle-btn");i&&(un=!un,i.hidden=!un,e==null||e.classList.toggle("active",un),un&&await cz())}async function cz(){const i=document.getElementById("plans-rows");if(i){i.innerHTML='<div class="threads-empty">Loading plans…</div>';try{const e=await Mi("plan_list",{}),t=e.ok&&Array.isArray(e.result)?e.result:[];if(!t.length){i.innerHTML='<div class="threads-empty">No plans yet — they appear here when the planner agent creates one.</div>';return}const n=[...t].sort((r,s)=>String(s.name||s.file||"").localeCompare(String(r.name||r.file||"")));i.innerHTML=n.map(r=>{var h,f;const s=String(r.name||r.file||""),o=String(r.path||`plan/${s}`),a=String(r.title||s).slice(0,60),l=r.steps_done??((h=r.progress)==null?void 0:h.done)??0,O=r.steps_total??((f=r.progress)==null?void 0:f.total)??0,c=O>0?`${l}/${O} steps`:"",d=r.is_current||r.current?" · ★ current":"";return`<div class="plan-row" onclick="window.__plansOpen('${g(o)}')" title="Open ${g(o)} in editor">
1015
+ </div>`}catch{return fs(i)}}async function az(){try{const i=await cs();Se.push(i),rt.set(i.id,[]),await ba(i.id)}catch(i){console.error("threadsNew failed",i)}}async function lz(){if(!ve)return;const i=Se.find(t=>t.id===ve);if(!i)return;const e=window.prompt("Rename thread:",i.title);if(!(e==null||e.trim()===""||e===i.title))try{const t=await ga(ve,{title:e.trim()});i.title=t.title;const n=document.getElementById("threads-detail-title");n&&(n.textContent=t.title),er()}catch(t){console.error("rename failed",t)}}function Oz(i){return i?i.status_hint==="done"||i.status_hint==="wont_do"||i.closure_reason==="done"||i.closure_reason==="wont_do":!1}async function md(){const i=document.getElementById("threads-reply-input");if(!i)return;let e=i.value.trim();if(!e)return;i.value="";const t=e.match(/^new topic:\s*(.*)$/is);if(t){const o=t[1].trim();try{const a=await cs(o.slice(0,80)||void 0);if(Se.push(a),rt.set(a.id,[]),await ba(a.id),!o)return;e=o}catch(a){console.error("New Topic spawn failed",a);return}}if(!t&&ve&&Oz(Se.find(o=>o.id===ve)))try{const o=await cs(e.slice(0,80)||void 0);Se.push(o),rt.set(o.id,[]),await ba(o.id)}catch(o){console.error("auto-new-thread on done reply failed",o);return}if(!ve)try{const o=await cs(e.slice(0,80));Se.push(o),rt.set(o.id,[]),ve=o.id}catch(o){console.error("auto-create failed",o);return}const n=ve,r=rt.get(n)||[];r.push({id:`local-${Date.now()}`,role:"user",content:e,timestamp:new Date().toISOString(),thread_id:n}),rt.set(n,r),pd(n),$a.add(n),er();const s=document.getElementById("threads-chat");Jn==null||Jn.abort(),Jn=new AbortController;try{await pz(e,n,Jn.signal,s)}catch(o){if((o==null?void 0:o.name)!=="AbortError"){const a=document.createElement("div");a.className="ai-msg ai-bot",a.style.color="var(--danger,#f38ba8)",a.textContent=`Connection failed: ${(o==null?void 0:o.message)||o}`,s==null||s.appendChild(a)}}finally{$a.delete(n),Jn=null;try{await AQ(n,!0)}catch{}await Qa(),pd(n);const o=Se.find(l=>l.id===n),a=document.getElementById("threads-detail-meta");if(o&&a){const l=o.sender?`<span>📨 from ${g(o.sender)}</span>`:"";a.innerHTML=`${hd(o.status_hint||"idle")} <span>${g(fd(o.last_message_at))}</span> ${l}`}}}queueMicrotask(()=>{const i=document.getElementById("threads-reply-form");i&&i.addEventListener("submit",t=>{t.preventDefault(),md()});const e=document.getElementById("threads-reply-input");e&&e.addEventListener("keydown",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),t.stopPropagation(),md())})}),window.__threadsShowList=()=>ya(),window.__threadsShowDetail=i=>{ba(i)},window.__threadsNew=()=>{az()},window.__threadsRetry=()=>{Qa(2).then(er)},window.__threadsRenameActive=()=>{lz()};let un=!1;async function jQ(){const i=document.getElementById("plans-panel"),e=document.getElementById("plans-toggle-btn");i&&(un=!un,i.hidden=!un,e==null||e.classList.toggle("active",un),un&&await cz())}async function cz(){const i=document.getElementById("plans-rows");if(i){i.innerHTML='<div class="threads-empty">Loading plans…</div>';try{const e=await Mi("plan_list",{}),t=e.ok&&Array.isArray(e.result)?e.result:[];if(!t.length){i.innerHTML='<div class="threads-empty">No plans yet — they appear here when the planner agent creates one.</div>';return}const n=[...t].sort((r,s)=>String(s.name||s.file||"").localeCompare(String(r.name||r.file||"")));i.innerHTML=n.map(r=>{var h,f;const s=String(r.name||r.file||""),o=String(r.path||`plan/${s}`),a=String(r.title||s).slice(0,60),l=r.steps_done??((h=r.progress)==null?void 0:h.done)??0,O=r.steps_total??((f=r.progress)==null?void 0:f.total)??0,c=O>0?`${l}/${O} steps`:"",d=r.is_current||r.current?" · ★ current":"";return`<div class="plan-row" onclick="window.__plansOpen('${g(o)}')" title="Open ${g(o)} in editor">
1016
1016
  <div class="plan-name">${g(a)}</div>
1017
1017
  <div class="plan-meta">${g(s)}${c?" · "+g(c):""}${d}</div>
1018
1018
  </div>`}).join("")}catch{i.innerHTML='<div class="threads-empty" style="color:var(--danger,#f38ba8)">Failed to load plans</div>'}}}async function dz(i){var t;try{await ui(i)}catch(n){console.error("plansOpen failed",n)}un=!1;const e=document.getElementById("plans-panel");e&&(e.hidden=!0),(t=document.getElementById("plans-toggle-btn"))==null||t.classList.remove("active")}window.__plansToggle=()=>{jQ()},window.__plansOpen=i=>{dz(i)};let ds=!1;async function hz(){const i=document.getElementById("grounding-panel"),e=document.getElementById("grounding-toggle-btn");i&&(un&&jQ(),ds=!ds,i.hidden=!ds,e==null||e.classList.toggle("active",ds),ds&&await MQ())}async function MQ(){const i=document.getElementById("grounding-body");if(!i)return;i.innerHTML='<div class="threads-empty">Loading…</div>';let e={};try{const r=await fetch("/__dev/api/grounding/status");r.ok&&(e=await r.json())}catch{}const t=g(e.url||"https://mcp.tina4.com"),n=e.configured?`<span style="color:var(--success,#a6e3a1)">&#9679; Configured</span> <span style="opacity:0.6">(…${g(e.last4||"")})</span>`:'<span style="color:var(--warn,#f9e2af)">&#9675; Not set</span> — using local corpus fallback';i.innerHTML=`
File without changes