tina4-python 3.11.22__tar.gz → 3.11.24__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 (153) hide show
  1. {tina4_python-3.11.22 → tina4_python-3.11.24}/PKG-INFO +1 -1
  2. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/__init__.py +1 -1
  3. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/dev_admin/__init__.py +231 -1
  4. tina4_python-3.11.24/tina4_python/docs.py +821 -0
  5. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/mcp/tools.py +31 -0
  6. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/js/tina4-dev-admin.js +260 -111
  7. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/js/tina4-dev-admin.min.js +260 -111
  8. {tina4_python-3.11.22 → tina4_python-3.11.24}/.gitignore +0 -0
  9. {tina4_python-3.11.22 → tina4_python-3.11.24}/README.md +0 -0
  10. {tina4_python-3.11.22 → tina4_python-3.11.24}/pyproject.toml +0 -0
  11. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/CLAUDE.md +0 -0
  12. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/HtmlElement.py +0 -0
  13. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/Testing.py +0 -0
  14. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/ai/__init__.py +0 -0
  15. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/api/__init__.py +0 -0
  16. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/auth/__init__.py +0 -0
  17. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/cache/__init__.py +0 -0
  18. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/cli/__init__.py +0 -0
  19. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/container/__init__.py +0 -0
  20. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/__init__.py +0 -0
  21. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/cache.py +0 -0
  22. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/constants.py +0 -0
  23. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/events.py +0 -0
  24. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/middleware.py +0 -0
  25. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/rate_limiter.py +0 -0
  26. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/request.py +0 -0
  27. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/response.py +0 -0
  28. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/router.py +0 -0
  29. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/server.py +0 -0
  30. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/crud/__init__.py +0 -0
  31. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/__init__.py +0 -0
  32. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/adapter.py +0 -0
  33. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/connection.py +0 -0
  34. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/firebird.py +0 -0
  35. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/mongodb.py +0 -0
  36. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/mssql.py +0 -0
  37. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/mysql.py +0 -0
  38. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/odbc.py +0 -0
  39. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/postgres.py +0 -0
  40. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/sqlite.py +0 -0
  41. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/debug/__init__.py +0 -0
  42. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/debug/error_overlay.py +0 -0
  43. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/dev_admin/metrics.py +0 -0
  44. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/dev_admin/plan.py +0 -0
  45. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/dev_admin/project_index.py +0 -0
  46. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/dotenv/__init__.py +0 -0
  47. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/frond/FROND.md +0 -0
  48. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/frond/__init__.py +0 -0
  49. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/frond/engine.py +0 -0
  50. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/auth/meta.json +0 -0
  51. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  52. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/database/meta.json +0 -0
  53. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  54. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/error-overlay/meta.json +0 -0
  55. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  56. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/orm/meta.json +0 -0
  57. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  58. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  59. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/queue/meta.json +0 -0
  60. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  61. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/rest-api/meta.json +0 -0
  62. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  63. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/templates/meta.json +0 -0
  64. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  65. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  66. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/graphql/__init__.py +0 -0
  67. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/i18n/__init__.py +0 -0
  68. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/mcp/__init__.py +0 -0
  69. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/mcp/protocol.py +0 -0
  70. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/messenger/__init__.py +0 -0
  71. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/migration/__init__.py +0 -0
  72. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/migration/runner.py +0 -0
  73. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/orm/__init__.py +0 -0
  74. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/orm/fields.py +0 -0
  75. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/orm/model.py +0 -0
  76. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/css/tina4.css +0 -0
  77. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/css/tina4.min.css +0 -0
  78. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/favicon.ico +0 -0
  79. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/images/logo.svg +0 -0
  80. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  81. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/js/frond.min.js +0 -0
  82. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/js/tina4.min.js +0 -0
  83. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/js/tina4js.min.js +0 -0
  84. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/swagger/index.html +0 -0
  85. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  86. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/query_builder/__init__.py +0 -0
  87. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue/__init__.py +0 -0
  88. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue/job.py +0 -0
  89. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue/kafka_backend.py +0 -0
  90. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue/lite_backend.py +0 -0
  91. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue/mongo_backend.py +0 -0
  92. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue/rabbitmq_backend.py +0 -0
  93. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue_backends/__init__.py +0 -0
  94. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue_backends/kafka_backend.py +0 -0
  95. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue_backends/mongo_backend.py +0 -0
  96. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  97. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/__init__.py +0 -0
  98. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  99. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_badges.scss +0 -0
  100. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  101. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_cards.scss +0 -0
  102. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_forms.scss +0 -0
  103. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_grid.scss +0 -0
  104. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_modals.scss +0 -0
  105. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_nav.scss +0 -0
  106. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_reset.scss +0 -0
  107. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_tables.scss +0 -0
  108. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_typography.scss +0 -0
  109. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  110. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_variables.scss +0 -0
  111. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/base.scss +0 -0
  112. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/colors.scss +0 -0
  113. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/tina4.scss +0 -0
  114. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/seeder/__init__.py +0 -0
  115. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/service/__init__.py +0 -0
  116. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/session/__init__.py +0 -0
  117. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/session_handlers/__init__.py +0 -0
  118. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  119. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/session_handlers/redis_handler.py +0 -0
  120. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/session_handlers/valkey_handler.py +0 -0
  121. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/swagger/__init__.py +0 -0
  122. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/components/crud.twig +0 -0
  123. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  124. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  125. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/docker/python/Dockerfile +0 -0
  126. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  127. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/302.twig +0 -0
  128. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/401.twig +0 -0
  129. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/403.twig +0 -0
  130. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/404.twig +0 -0
  131. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/500.twig +0 -0
  132. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/502.twig +0 -0
  133. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/503.twig +0 -0
  134. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/base.twig +0 -0
  135. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/frontend/README.md +0 -0
  136. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/readme.md +0 -0
  137. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/test_client/__init__.py +0 -0
  138. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  139. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  140. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  141. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  142. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  143. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  144. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  145. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  146. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  147. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  148. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  149. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  150. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/validator/__init__.py +0 -0
  151. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/websocket/__init__.py +0 -0
  152. {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/websocket/backplane.py +0 -0
  153. {tina4_python-3.11.22 → tina4_python-3.11.24}/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.11.22
3
+ Version: 3.11.24
4
4
  Summary: Tina4 for Python — 54 built-in features, zero dependencies
5
5
  Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
6
6
  License: MIT
@@ -8,7 +8,7 @@ Tina4 Python v3.0 — Zero-dependency, lightweight web framework.
8
8
 
9
9
  One import, everything works.
10
10
  """
11
- __version__ = "3.11.22"
11
+ __version__ = "3.11.24"
12
12
 
13
13
  # ── Route decorators ──
14
14
  from tina4_python.core.router import ( # noqa: E402, F401
@@ -288,6 +288,94 @@ def register():
288
288
  Router.get(path, handler)
289
289
  else:
290
290
  Router.post(path, handler)
291
+ # Auto-discovery: drop `.tina4/mcp.json` so MCP-aware AI tools
292
+ # (Claude Code, Cursor, etc.) discover the local Live Docs +
293
+ # MCP server without the user authoring config. Idempotent.
294
+ write_mcp_discovery_file()
295
+
296
+
297
+ def write_mcp_discovery_file() -> None:
298
+ """Drop `.tina4/mcp.json` and append `.tina4/` to `.gitignore`.
299
+
300
+ Both are idempotent — running twice is a no-op when the state is
301
+ already correct. Skipped silently outside debug mode and on
302
+ filesystem errors (read-only project dir, etc.) — discovery is
303
+ a convenience, not a requirement.
304
+
305
+ See plan/v3/22-LIVE-API-RAG.md §"Auto-discovery file" for the
306
+ JSON shape.
307
+ """
308
+ import json
309
+ import os
310
+
311
+ is_dev = os.environ.get("TINA4_DEBUG", "false").lower() in ("1", "true", "yes")
312
+ if not is_dev:
313
+ return
314
+ root = os.getcwd()
315
+ tina4_dir = os.path.join(root, ".tina4")
316
+ mcp_file = os.path.join(tina4_dir, "mcp.json")
317
+ port = (os.environ.get("TINA4_PORT")
318
+ or os.environ.get("PORT")
319
+ or "7146")
320
+ expected = {
321
+ "mcpServers": {
322
+ "tina4-live-docs": {
323
+ "url": f"http://localhost:{port}/__dev/api/mcp",
324
+ "description": "Live API docs for this Tina4 project (framework + user code)",
325
+ }
326
+ }
327
+ }
328
+ expected_json = json.dumps(expected, indent=2) + "\n"
329
+
330
+ try:
331
+ if os.path.isfile(mcp_file):
332
+ with open(mcp_file, "r", encoding="utf-8") as f:
333
+ existing = f.read()
334
+ if existing.strip() == expected_json.strip():
335
+ _ensure_gitignore(root)
336
+ return
337
+ os.makedirs(tina4_dir, exist_ok=True)
338
+ with open(mcp_file, "w", encoding="utf-8") as f:
339
+ f.write(expected_json)
340
+ _ensure_gitignore(root)
341
+ except OSError:
342
+ # Read-only fs, permission denied, etc. Silently skip —
343
+ # discovery is convenience.
344
+ return
345
+
346
+
347
+ def _ensure_gitignore(root: str) -> None:
348
+ """Append `.tina4/` to `.gitignore` if not already excluded.
349
+
350
+ Tolerates leading slashes, trailing slashes, and existing comment
351
+ lines so we never duplicate. Only touches `.gitignore` if `.git/`
352
+ exists (don't pollute non-git projects).
353
+ """
354
+ import os
355
+
356
+ if not os.path.isdir(os.path.join(root, ".git")):
357
+ return
358
+ gi_path = os.path.join(root, ".gitignore")
359
+ existing = ""
360
+ if os.path.isfile(gi_path):
361
+ try:
362
+ with open(gi_path, "r", encoding="utf-8") as f:
363
+ existing = f.read()
364
+ except OSError:
365
+ return
366
+ for raw in existing.splitlines():
367
+ line = raw.strip()
368
+ if not line or line.startswith("#"):
369
+ continue
370
+ normal = line.strip("/").strip()
371
+ if normal == ".tina4":
372
+ return # already excluded
373
+ suffix = "" if existing.endswith("\n") or existing == "" else "\n"
374
+ try:
375
+ with open(gi_path, "a", encoding="utf-8") as f:
376
+ f.write(suffix + ".tina4/\n")
377
+ except OSError:
378
+ pass
291
379
 
292
380
 
293
381
  def get_api_handlers() -> dict:
@@ -388,6 +476,17 @@ def get_api_handlers() -> dict:
388
476
  # without shelling out from the browser.
389
477
  "/__dev/api/scaffold": ("GET", _api_scaffold_list),
390
478
  "/__dev/api/scaffold/run": ("POST", _api_scaffold_run),
479
+ # ── Live Docs (per plan/v3/22-LIVE-API-RAG.md) ──
480
+ # Thin HTTP wrappers around tina4_python.docs.Docs. Both
481
+ # framework public API and the user's src/ surface are
482
+ # returned, tagged with `source = framework | user`. AI tools
483
+ # (Claude Code, Cursor, dev-admin chat) hit these for ground-
484
+ # truth introspection instead of guessing from training data.
485
+ "/__dev/api/docs/search": ("GET", _api_docs_search),
486
+ "/__dev/api/docs/class": ("GET", _api_docs_class),
487
+ "/__dev/api/docs/method": ("GET", _api_docs_method),
488
+ "/__dev/api/docs/index": ("GET", _api_docs_index),
489
+ "/__dev/api/docs/.well-known.json": ("GET", _api_docs_well_known),
391
490
  }
392
491
 
393
492
 
@@ -1766,10 +1865,56 @@ def render_dev_toolbar(method: str, path: str, matched_pattern: str,
1766
1865
  <span style="color:#ffeb3b;">req:{request_id}</span>
1767
1866
  <span style="color:#90caf9;">{route_count} routes</span>
1768
1867
  <span style="color:#888;">Python {python_version}</span>
1769
- <a href="#" onclick="(function(e){{e.preventDefault();var p=document.getElementById('tina4-dev-panel');if(p){{p.style.display=p.style.display==='none'?'block':'none';return;}}var c=document.createElement('div');c.id='tina4-dev-panel';c.style.cssText='position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';var f=document.createElement('iframe');f.src='/__dev';f.style.cssText='width:100%;height:100%;border:1px solid #3572A5;border-radius:0.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);background:#0f172a';c.appendChild(f);document.body.appendChild(c);}})(event)" style="color:#ef9a9a;margin-left:auto;text-decoration:none;cursor:pointer;">Dashboard &#8599;</a>
1868
+ <a href="#" onclick="window.__tina4ToggleOverlay(event)" style="color:#ef9a9a;margin-left:auto;text-decoration:none;cursor:pointer;">Dashboard &#8599;</a>
1770
1869
  <span onclick="this.parentElement.style.display='none'" style="cursor:pointer;color:#888;margin-left:8px;">&#10005;</span>
1771
1870
  </div>
1772
1871
  <script>
1872
+ // Overlay open/toggle helper + auto-restore. Persist the dev-admin
1873
+ // iframe's open/closed state across parent reloads so saving a file
1874
+ // (which kicks the watcher → location.reload) doesn't lose the
1875
+ // user's dev-admin chat / plan / file tree. Cross-framework parity
1876
+ // with PHP / Ruby / Node — same localStorage key, same shape.
1877
+ (function(){{
1878
+ var STATE_KEY = 'tina4_dev_overlay_open';
1879
+ function buildOverlay() {{
1880
+ var c = document.createElement('div');
1881
+ c.id = 'tina4-dev-panel';
1882
+ c.style.cssText = 'position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';
1883
+ var f = document.createElement('iframe');
1884
+ f.src = '/__dev';
1885
+ f.style.cssText = 'width:100%;height:100%;border:1px solid #3572A5;border-radius:0.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);background:#0f172a';
1886
+ c.appendChild(f);
1887
+ document.body.appendChild(c);
1888
+ return c;
1889
+ }}
1890
+ window.__tina4ToggleOverlay = function(e) {{
1891
+ if (e) e.preventDefault();
1892
+ var p = document.getElementById('tina4-dev-panel');
1893
+ if (p) {{
1894
+ var hide = p.style.display !== 'none';
1895
+ p.style.display = hide ? 'none' : 'block';
1896
+ try {{ localStorage.setItem(STATE_KEY, hide ? '0' : '1'); }} catch (_) {{}}
1897
+ return;
1898
+ }}
1899
+ buildOverlay();
1900
+ try {{ localStorage.setItem(STATE_KEY, '1'); }} catch (_) {{}}
1901
+ }};
1902
+ function restoreIfOpen() {{
1903
+ try {{
1904
+ if (location.pathname.indexOf('/__dev') === 0) return;
1905
+ if (localStorage.getItem(STATE_KEY) === '1' && !document.getElementById('tina4-dev-panel')) {{
1906
+ buildOverlay();
1907
+ }}
1908
+ }} catch (_) {{}}
1909
+ }}
1910
+ if (document.readyState === 'loading') {{
1911
+ document.addEventListener('DOMContentLoaded', restoreIfOpen);
1912
+ }} else {{
1913
+ restoreIfOpen();
1914
+ }}
1915
+ }})();
1916
+ </script>
1917
+ <script>
1773
1918
  {'(function(){})();' if no_reload else f"""(function(){{
1774
1919
  var _t4_mtime=0,_t4_css_exts=['.css','.scss'],_t4_debounce=null;
1775
1920
  var _t4_interval=parseInt('{poll_interval_ms}')||3000;
@@ -2582,5 +2727,90 @@ async def _api_scaffold_run(request, response):
2582
2727
  return response({"ok": False, "error": str(exc)}, 500)
2583
2728
 
2584
2729
 
2730
+ _DOCS_SINGLETON = None # cached per-process so the framework index
2731
+ # builds once. User portion still mtime-refreshes
2732
+ # inside Docs.
2733
+
2734
+ def _docs_instance():
2735
+ """Lazy singleton for the Live Docs module — bound to the project
2736
+ cwd at first call. Subsequent calls reuse the same Docs instance,
2737
+ which keeps the framework index hot across requests while still
2738
+ refreshing the user portion when src/ files change."""
2739
+ global _DOCS_SINGLETON
2740
+ if _DOCS_SINGLETON is None:
2741
+ import os
2742
+ from tina4_python.docs import Docs
2743
+ _DOCS_SINGLETON = Docs(project_root=os.getcwd())
2744
+ return _DOCS_SINGLETON
2745
+
2746
+
2747
+ async def _api_docs_search(request, response):
2748
+ """GET /__dev/api/docs/search?q=...&k=...&source=...&include_private=..."""
2749
+ q = (request.query.get("q") or "").strip() if hasattr(request, "query") else ""
2750
+ if not q:
2751
+ return response({"ok": False, "error": "missing required 'q' param"}, 400)
2752
+ try:
2753
+ k = int(request.query.get("k", 5))
2754
+ except (TypeError, ValueError):
2755
+ k = 5
2756
+ source = request.query.get("source", "all")
2757
+ include_private = (request.query.get("include_private", "")
2758
+ or "").lower() in ("1", "true", "yes")
2759
+ import time
2760
+ t0 = time.perf_counter()
2761
+ hits = _docs_instance().search(q, k=k, source=source, include_private=include_private)
2762
+ took_ms = int((time.perf_counter() - t0) * 1000)
2763
+ return response({"ok": True, "query": q, "results": hits, "took_ms": took_ms})
2764
+
2765
+
2766
+ async def _api_docs_class(request, response):
2767
+ """GET /__dev/api/docs/class?name=<fqn>"""
2768
+ name = (request.query.get("name") or "").strip()
2769
+ if not name:
2770
+ return response({"ok": False, "error": "missing required 'name' param"}, 400)
2771
+ spec = _docs_instance().class_spec(name)
2772
+ if spec is None:
2773
+ return response({"ok": False, "error": f"class not found: {name}"}, 404)
2774
+ return response({"ok": True, "class": spec})
2775
+
2776
+
2777
+ async def _api_docs_method(request, response):
2778
+ """GET /__dev/api/docs/method?class=<fqn>&name=<method>"""
2779
+ cls = (request.query.get("class") or "").strip()
2780
+ name = (request.query.get("name") or "").strip()
2781
+ if not cls or not name:
2782
+ return response({"ok": False, "error": "both 'class' and 'name' params are required"}, 400)
2783
+ spec = _docs_instance().method_spec(cls, name)
2784
+ if spec is None:
2785
+ return response({"ok": False, "error": f"method not found: {cls}.{name}"}, 404)
2786
+ return response({"ok": True, "method": spec})
2787
+
2788
+
2789
+ async def _api_docs_index(request, response):
2790
+ """GET /__dev/api/docs/index?source=<framework|user|all>"""
2791
+ source = request.query.get("source", "all")
2792
+ entities = _docs_instance().index()
2793
+ if source != "all":
2794
+ entities = [e for e in entities if e.get("source") == source]
2795
+ return response({"ok": True, "count": len(entities), "entities": entities})
2796
+
2797
+
2798
+ async def _api_docs_well_known(request, response):
2799
+ """Public well-known doc — describes what the docs surface offers
2800
+ so non-MCP AI tools know what endpoints to call."""
2801
+ return response({
2802
+ "ok": True,
2803
+ "service": "tina4-live-docs",
2804
+ "version": "1",
2805
+ "endpoints": {
2806
+ "search": "/__dev/api/docs/search?q={query}&k={int}&source={framework|user|all}",
2807
+ "class": "/__dev/api/docs/class?name={fqn}",
2808
+ "method": "/__dev/api/docs/method?class={fqn}&name={method}",
2809
+ "index": "/__dev/api/docs/index?source={framework|user|all}",
2810
+ },
2811
+ "description": "Live API reflection for this Tina4 project — framework + user code combined.",
2812
+ })
2813
+
2814
+
2585
2815
  __all__ = ["MessageLog", "RequestInspector", "BrokenTracker",
2586
2816
  "get_api_handlers", "render_dev_toolbar"]