MetaList 0.19.0__tar.gz → 0.20.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 (569) hide show
  1. {metalist-0.19.0/metalist.egg-info → metalist-0.20.0}/PKG-INFO +12 -1
  2. {metalist-0.19.0 → metalist-0.20.0}/README.md +11 -0
  3. {metalist-0.19.0 → metalist-0.20.0}/app/api/note_requests.py +30 -0
  4. {metalist-0.19.0 → metalist-0.20.0}/app/api/routes/ai.py +275 -21
  5. {metalist-0.19.0 → metalist-0.20.0}/app/api/routes/notes.py +127 -1
  6. {metalist-0.19.0 → metalist-0.20.0}/app/exception_boundaries.py +1 -0
  7. {metalist-0.19.0 → metalist-0.20.0}/app/presentation/render/note_renderer.py +1 -1
  8. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/cloud_privacy.py +52 -19
  9. metalist-0.20.0/app/services/agent/export_title_suggestions.py +187 -0
  10. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/failure_explanations.py +1 -0
  11. metalist-0.20.0/app/services/agent/note_tag_suggestions.py +318 -0
  12. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/scope.py +4 -2
  13. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/help-ai.md +32 -0
  14. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/help-data.md +9 -2
  15. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/help-formatting.md +5 -4
  16. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/help-menus.md +2 -1
  17. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/help-notes.md +3 -2
  18. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/help-privacy.md +6 -4
  19. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/help-releases.md +11 -0
  20. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/help-tags.md +5 -3
  21. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/structured_inference.py +2 -0
  22. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/tagging_run.py +43 -25
  23. {metalist-0.19.0 → metalist-0.20.0}/app/services/ai_chat_rendering.py +2 -2
  24. metalist-0.20.0/app/services/ai_setup.py +84 -0
  25. {metalist-0.19.0 → metalist-0.20.0}/app/services/content_formatting.py +40 -20
  26. {metalist-0.19.0 → metalist-0.20.0}/app/services/diagnostics.py +7 -1
  27. {metalist-0.19.0 → metalist-0.20.0}/app/services/embedded_references.py +51 -49
  28. metalist-0.20.0/app/services/html_export.py +652 -0
  29. {metalist-0.19.0 → metalist-0.20.0}/app/services/note_fullscreen.py +1 -1
  30. {metalist-0.19.0 → metalist-0.20.0}/app/services/note_store.py +21 -0
  31. {metalist-0.19.0 → metalist-0.20.0}/app/services/search_index.py +36 -14
  32. {metalist-0.19.0 → metalist-0.20.0}/app/services/snapshot.py +23 -20
  33. {metalist-0.19.0 → metalist-0.20.0}/app/services/undo_state.py +88 -1
  34. {metalist-0.19.0 → metalist-0.20.0}/app/services/windows_process_control.py +6 -2
  35. {metalist-0.19.0 → metalist-0.20.0}/app/startup_sanity_config.py +2 -0
  36. {metalist-0.19.0 → metalist-0.20.0}/app/static/config/agent-menu-actions.json +6 -0
  37. {metalist-0.19.0 → metalist-0.20.0}/app/static/config/command_palette_tags.json +4 -0
  38. {metalist-0.19.0 → metalist-0.20.0}/app/static/css/main.css +260 -33
  39. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/ai-chat-api.js +157 -0
  40. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/api-client.js +127 -14
  41. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/command-palette/command-palette-controller.js +12 -65
  42. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/command-palette/endpoint-registry.js +7 -0
  43. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/config.js +8 -0
  44. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/context-menu/context-menu-registry.js +72 -0
  45. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/context-menu/context-menu-service.js +24 -0
  46. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/context-menu/input-caret-service.js +15 -3
  47. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/error-handler.js +3 -1
  48. metalist-0.20.0/app/static/js/modules/modals/export-options-modal.js +314 -0
  49. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/actions/content-actions.js +3 -1
  50. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/actions/note-actions.js +2 -1
  51. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/actions/ui-actions.js +15 -1
  52. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/events/context-menu-events.js +402 -260
  53. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/events/mouse-events.js +65 -4
  54. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/command-gate-service.js +12 -0
  55. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/differential-view-service.js +10 -0
  56. metalist-0.20.0/app/static/js/modules/mode-manager/services/export-service.js +64 -0
  57. metalist-0.20.0/app/static/js/modules/mode-manager/services/export-title-service.js +59 -0
  58. metalist-0.20.0/app/static/js/modules/mode-manager/services/print-service.js +82 -0
  59. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/tag-bar-service.js +34 -1
  60. metalist-0.20.0/app/static/js/modules/mode-manager/services/tag-proposal-service.js +359 -0
  61. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/viewport-hold-service.js +72 -0
  62. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/prefetch_link_titles.py +1 -1
  63. metalist-0.20.0/app/usecases/tag_proposals.py +390 -0
  64. {metalist-0.19.0 → metalist-0.20.0}/app/version.py +1 -1
  65. {metalist-0.19.0 → metalist-0.20.0}/main.py +24 -1
  66. {metalist-0.19.0 → metalist-0.20.0/metalist.egg-info}/PKG-INFO +12 -1
  67. {metalist-0.19.0 → metalist-0.20.0}/metalist.egg-info/SOURCES.txt +7 -0
  68. metalist-0.19.0/app/services/html_export.py +0 -327
  69. metalist-0.19.0/app/static/js/modules/mode-manager/services/tag-proposal-service.js +0 -141
  70. metalist-0.19.0/app/usecases/tag_proposals.py +0 -173
  71. {metalist-0.19.0 → metalist-0.20.0}/MANIFEST.in +0 -0
  72. {metalist-0.19.0 → metalist-0.20.0}/app/__init__.py +0 -0
  73. {metalist-0.19.0 → metalist-0.20.0}/app/api/__init__.py +0 -0
  74. {metalist-0.19.0 → metalist-0.20.0}/app/api/deps.py +0 -0
  75. {metalist-0.19.0 → metalist-0.20.0}/app/api/dev.py +0 -0
  76. {metalist-0.19.0 → metalist-0.20.0}/app/api/middleware/auth.py +0 -0
  77. {metalist-0.19.0 → metalist-0.20.0}/app/api/request_auth.py +0 -0
  78. {metalist-0.19.0 → metalist-0.20.0}/app/api/routes/app_updates.py +0 -0
  79. {metalist-0.19.0 → metalist-0.20.0}/app/api/routes/auth.py +0 -0
  80. {metalist-0.19.0 → metalist-0.20.0}/app/api/routes/backups.py +0 -0
  81. {metalist-0.19.0 → metalist-0.20.0}/app/api/routes/files.py +0 -0
  82. {metalist-0.19.0 → metalist-0.20.0}/app/api/routes/ontology.py +0 -0
  83. {metalist-0.19.0 → metalist-0.20.0}/app/api/routes/reminders.py +0 -0
  84. {metalist-0.19.0 → metalist-0.20.0}/app/api/routes/remote_images.py +0 -0
  85. {metalist-0.19.0 → metalist-0.20.0}/app/api/routes/test.py +0 -0
  86. {metalist-0.19.0 → metalist-0.20.0}/app/api/transactions.py +0 -0
  87. {metalist-0.19.0 → metalist-0.20.0}/app/api/upload_limits.py +0 -0
  88. {metalist-0.19.0 → metalist-0.20.0}/app/config.py +0 -0
  89. {metalist-0.19.0 → metalist-0.20.0}/app/data_directory.py +0 -0
  90. {metalist-0.19.0 → metalist-0.20.0}/app/db/__init__.py +0 -0
  91. {metalist-0.19.0 → metalist-0.20.0}/app/db/backup_settings_sql.py +0 -0
  92. {metalist-0.19.0 → metalist-0.20.0}/app/db/editable_files_migration.py +0 -0
  93. {metalist-0.19.0 → metalist-0.20.0}/app/db/engine.py +0 -0
  94. {metalist-0.19.0 → metalist-0.20.0}/app/db/file_schema.py +0 -0
  95. {metalist-0.19.0 → metalist-0.20.0}/app/db/file_session.py +0 -0
  96. {metalist-0.19.0 → metalist-0.20.0}/app/db/files_sql.py +0 -0
  97. {metalist-0.19.0 → metalist-0.20.0}/app/db/link_titles_sql.py +0 -0
  98. {metalist-0.19.0 → metalist-0.20.0}/app/db/live_recovery.py +0 -0
  99. {metalist-0.19.0 → metalist-0.20.0}/app/db/migrations.py +0 -0
  100. {metalist-0.19.0 → metalist-0.20.0}/app/db/notes_sql.py +0 -0
  101. {metalist-0.19.0 → metalist-0.20.0}/app/db/ontology_rules_sql.py +0 -0
  102. {metalist-0.19.0 → metalist-0.20.0}/app/db/reminders_sql.py +0 -0
  103. {metalist-0.19.0 → metalist-0.20.0}/app/db/retire_sounds.py +0 -0
  104. {metalist-0.19.0 → metalist-0.20.0}/app/db/schema.py +0 -0
  105. {metalist-0.19.0 → metalist-0.20.0}/app/db/search_history_sql.py +0 -0
  106. {metalist-0.19.0 → metalist-0.20.0}/app/db/session.py +0 -0
  107. {metalist-0.19.0 → metalist-0.20.0}/app/db/settings_sql.py +0 -0
  108. {metalist-0.19.0 → metalist-0.20.0}/app/db/tab_state_sql.py +0 -0
  109. {metalist-0.19.0 → metalist-0.20.0}/app/db/version.py +0 -0
  110. {metalist-0.19.0 → metalist-0.20.0}/app/encryption_audit.py +0 -0
  111. {metalist-0.19.0 → metalist-0.20.0}/app/https_proxy.py +0 -0
  112. {metalist-0.19.0 → metalist-0.20.0}/app/main.py +0 -0
  113. {metalist-0.19.0 → metalist-0.20.0}/app/models/commands.py +0 -0
  114. {metalist-0.19.0 → metalist-0.20.0}/app/models/database.py +0 -0
  115. {metalist-0.19.0 → metalist-0.20.0}/app/models/entities.py +0 -0
  116. {metalist-0.19.0 → metalist-0.20.0}/app/models/enums.py +0 -0
  117. {metalist-0.19.0 → metalist-0.20.0}/app/models/linked_list.py +0 -0
  118. {metalist-0.19.0 → metalist-0.20.0}/app/models/list_operations.py +0 -0
  119. {metalist-0.19.0 → metalist-0.20.0}/app/models/list_traversal.py +0 -0
  120. {metalist-0.19.0 → metalist-0.20.0}/app/models/note_crud.py +0 -0
  121. {metalist-0.19.0 → metalist-0.20.0}/app/models/utils.py +0 -0
  122. {metalist-0.19.0 → metalist-0.20.0}/app/network_settings.py +0 -0
  123. {metalist-0.19.0 → metalist-0.20.0}/app/presentation/__init__.py +0 -0
  124. {metalist-0.19.0 → metalist-0.20.0}/app/presentation/login_page.py +0 -0
  125. {metalist-0.19.0 → metalist-0.20.0}/app/presentation/render/__init__.py +0 -0
  126. {metalist-0.19.0 → metalist-0.20.0}/app/presentation/templates.py +0 -0
  127. {metalist-0.19.0 → metalist-0.20.0}/app/security/authenticated_logging.py +0 -0
  128. {metalist-0.19.0 → metalist-0.20.0}/app/security/encryption.py +0 -0
  129. {metalist-0.19.0 → metalist-0.20.0}/app/security/http_error_diagnostics.py +0 -0
  130. {metalist-0.19.0 → metalist-0.20.0}/app/security/http_headers.py +0 -0
  131. {metalist-0.19.0 → metalist-0.20.0}/app/security/note_html.py +0 -0
  132. {metalist-0.19.0 → metalist-0.20.0}/app/security/request_boundary.py +0 -0
  133. {metalist-0.19.0 → metalist-0.20.0}/app/security/request_hosts.py +0 -0
  134. {metalist-0.19.0 → metalist-0.20.0}/app/security/sensitive_cache.py +0 -0
  135. {metalist-0.19.0 → metalist-0.20.0}/app/security/sensitive_logging.py +0 -0
  136. {metalist-0.19.0 → metalist-0.20.0}/app/security/shell_execution.py +0 -0
  137. {metalist-0.19.0 → metalist-0.20.0}/app/security/validation_errors.py +0 -0
  138. {metalist-0.19.0 → metalist-0.20.0}/app/server_runtime.py +0 -0
  139. {metalist-0.19.0 → metalist-0.20.0}/app/services/__init__.py +0 -0
  140. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/__init__.py +0 -0
  141. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/action_log.py +0 -0
  142. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/agent_loop.py +0 -0
  143. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/agent_tools.py +0 -0
  144. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/citation_tokens.py +0 -0
  145. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/context.py +0 -0
  146. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/evidence_serialization.py +0 -0
  147. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/execution_errors.py +0 -0
  148. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/help_catalog.py +0 -0
  149. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/history.py +0 -0
  150. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/inference.py +0 -0
  151. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/investigation.py +0 -0
  152. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/judging.py +0 -0
  153. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/menu_actions.py +0 -0
  154. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/model_policy.py +0 -0
  155. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/note_aliases.py +0 -0
  156. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/openai_cost_tracking.py +0 -0
  157. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/openai_inference.py +0 -0
  158. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/prompt_settings.py +0 -0
  159. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/prompts/__init__.py +0 -0
  160. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/prompts/agent.md +0 -0
  161. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/prompts/final-response.md +0 -0
  162. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/prompts/system.md +0 -0
  163. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/release_notes.py +0 -0
  164. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/retrieval_settings.py +0 -0
  165. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/runtime.py +0 -0
  166. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skill_settings.py +0 -0
  167. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/__init__.py +0 -0
  168. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/help-dictation.md +0 -0
  169. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/help-references.md +0 -0
  170. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/help-reminders.md +0 -0
  171. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/help-search.md +0 -0
  172. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/staged-summary.md +0 -0
  173. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/tag-proposals.md +0 -0
  174. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/skills/web-browsing.md +0 -0
  175. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/staged_summary.py +0 -0
  176. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/strict_structured_outputs.py +0 -0
  177. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/summary_batch_store.py +0 -0
  178. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/tagging.py +0 -0
  179. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/token_estimation.py +0 -0
  180. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/tool_calling.py +0 -0
  181. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/trace.py +0 -0
  182. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/web_actions.py +0 -0
  183. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/web_capabilities.py +0 -0
  184. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/web_evidence.py +0 -0
  185. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/web_fetch.py +0 -0
  186. {metalist-0.19.0 → metalist-0.20.0}/app/services/agent/web_settings.py +0 -0
  187. {metalist-0.19.0 → metalist-0.20.0}/app/services/ai_chat.py +0 -0
  188. {metalist-0.19.0 → metalist-0.20.0}/app/services/ai_chat_stream.py +0 -0
  189. {metalist-0.19.0 → metalist-0.20.0}/app/services/app_update_worker.py +0 -0
  190. {metalist-0.19.0 → metalist-0.20.0}/app/services/app_updates.py +0 -0
  191. {metalist-0.19.0 → metalist-0.20.0}/app/services/auth.py +0 -0
  192. {metalist-0.19.0 → metalist-0.20.0}/app/services/auth_cache_state.py +0 -0
  193. {metalist-0.19.0 → metalist-0.20.0}/app/services/auth_service.py +0 -0
  194. {metalist-0.19.0 → metalist-0.20.0}/app/services/backlink_index.py +0 -0
  195. {metalist-0.19.0 → metalist-0.20.0}/app/services/backlinks.py +0 -0
  196. {metalist-0.19.0 → metalist-0.20.0}/app/services/backup_service.py +0 -0
  197. {metalist-0.19.0 → metalist-0.20.0}/app/services/backup_settings_service.py +0 -0
  198. {metalist-0.19.0 → metalist-0.20.0}/app/services/bulk_operation.py +0 -0
  199. {metalist-0.19.0 → metalist-0.20.0}/app/services/chess_rendering.py +0 -0
  200. {metalist-0.19.0 → metalist-0.20.0}/app/services/client_state_service.py +0 -0
  201. {metalist-0.19.0 → metalist-0.20.0}/app/services/code_rendering.py +0 -0
  202. {metalist-0.19.0 → metalist-0.20.0}/app/services/content_cache.py +0 -0
  203. {metalist-0.19.0 → metalist-0.20.0}/app/services/dictation_cleanup.py +0 -0
  204. {metalist-0.19.0 → metalist-0.20.0}/app/services/diff_rendering.py +0 -0
  205. {metalist-0.19.0 → metalist-0.20.0}/app/services/encryption.py +0 -0
  206. {metalist-0.19.0 → metalist-0.20.0}/app/services/exception_capture.py +0 -0
  207. {metalist-0.19.0 → metalist-0.20.0}/app/services/file_edit_sessions.py +0 -0
  208. {metalist-0.19.0 → metalist-0.20.0}/app/services/file_registry.py +0 -0
  209. {metalist-0.19.0 → metalist-0.20.0}/app/services/file_storage.py +0 -0
  210. {metalist-0.19.0 → metalist-0.20.0}/app/services/file_text_preview.py +0 -0
  211. {metalist-0.19.0 → metalist-0.20.0}/app/services/footnote_layout.py +0 -0
  212. {metalist-0.19.0 → metalist-0.20.0}/app/services/footnote_rendering.py +0 -0
  213. {metalist-0.19.0 → metalist-0.20.0}/app/services/hierarchy.py +0 -0
  214. {metalist-0.19.0 → metalist-0.20.0}/app/services/html_unformatting.py +0 -0
  215. {metalist-0.19.0 → metalist-0.20.0}/app/services/hydration_state.py +0 -0
  216. {metalist-0.19.0 → metalist-0.20.0}/app/services/image_size_formatting.py +0 -0
  217. {metalist-0.19.0 → metalist-0.20.0}/app/services/inline_image_dimensions.py +0 -0
  218. {metalist-0.19.0 → metalist-0.20.0}/app/services/inline_image_occurrences.py +0 -0
  219. {metalist-0.19.0 → metalist-0.20.0}/app/services/input_errors.py +0 -0
  220. {metalist-0.19.0 → metalist-0.20.0}/app/services/integrity.py +0 -0
  221. {metalist-0.19.0 → metalist-0.20.0}/app/services/latex_rendering.py +0 -0
  222. {metalist-0.19.0 → metalist-0.20.0}/app/services/link_title_retry.py +0 -0
  223. {metalist-0.19.0 → metalist-0.20.0}/app/services/link_titles.py +0 -0
  224. {metalist-0.19.0 → metalist-0.20.0}/app/services/login_rate_limit.py +0 -0
  225. {metalist-0.19.0 → metalist-0.20.0}/app/services/maintenance_mode.py +0 -0
  226. {metalist-0.19.0 → metalist-0.20.0}/app/services/markdown_rendering.py +0 -0
  227. {metalist-0.19.0 → metalist-0.20.0}/app/services/mermaid_note_rendering.py +0 -0
  228. {metalist-0.19.0 → metalist-0.20.0}/app/services/namespace_deletion_jobs.py +0 -0
  229. {metalist-0.19.0 → metalist-0.20.0}/app/services/namespace_deletion_worker.py +0 -0
  230. {metalist-0.19.0 → metalist-0.20.0}/app/services/namespace_rename_jobs.py +0 -0
  231. {metalist-0.19.0 → metalist-0.20.0}/app/services/namespace_rename_worker.py +0 -0
  232. {metalist-0.19.0 → metalist-0.20.0}/app/services/namespace_runtime_guard.py +0 -0
  233. {metalist-0.19.0 → metalist-0.20.0}/app/services/namespace_switcher.py +0 -0
  234. {metalist-0.19.0 → metalist-0.20.0}/app/services/note_image_tags.py +0 -0
  235. {metalist-0.19.0 → metalist-0.20.0}/app/services/note_ordering.py +0 -0
  236. {metalist-0.19.0 → metalist-0.20.0}/app/services/ontology_rules_store.py +0 -0
  237. {metalist-0.19.0 → metalist-0.20.0}/app/services/openai_credentials.py +0 -0
  238. {metalist-0.19.0 → metalist-0.20.0}/app/services/password_note_fields.py +0 -0
  239. {metalist-0.19.0 → metalist-0.20.0}/app/services/public_http.py +0 -0
  240. {metalist-0.19.0 → metalist-0.20.0}/app/services/reference_presentation.py +0 -0
  241. {metalist-0.19.0 → metalist-0.20.0}/app/services/reminders.py +0 -0
  242. {metalist-0.19.0 → metalist-0.20.0}/app/services/remote_image_proxy.py +0 -0
  243. {metalist-0.19.0 → metalist-0.20.0}/app/services/request_recovery.py +0 -0
  244. {metalist-0.19.0 → metalist-0.20.0}/app/services/resource_limits.py +0 -0
  245. {metalist-0.19.0 → metalist-0.20.0}/app/services/root_sorting.py +0 -0
  246. {metalist-0.19.0 → metalist-0.20.0}/app/services/runtime_generation.py +0 -0
  247. {metalist-0.19.0 → metalist-0.20.0}/app/services/runtime_hardening.py +0 -0
  248. {metalist-0.19.0 → metalist-0.20.0}/app/services/runtime_lock.py +0 -0
  249. {metalist-0.19.0 → metalist-0.20.0}/app/services/search_history.py +0 -0
  250. {metalist-0.19.0 → metalist-0.20.0}/app/services/search_history_storage.py +0 -0
  251. {metalist-0.19.0 → metalist-0.20.0}/app/services/search_query.py +0 -0
  252. {metalist-0.19.0 → metalist-0.20.0}/app/services/search_text.py +0 -0
  253. {metalist-0.19.0 → metalist-0.20.0}/app/services/selected_text_tag.py +0 -0
  254. {metalist-0.19.0 → metalist-0.20.0}/app/services/self_update.py +0 -0
  255. {metalist-0.19.0 → metalist-0.20.0}/app/services/session_timeout_service.py +0 -0
  256. {metalist-0.19.0 → metalist-0.20.0}/app/services/shell_session_service.py +0 -0
  257. {metalist-0.19.0 → metalist-0.20.0}/app/services/store.py +0 -0
  258. {metalist-0.19.0 → metalist-0.20.0}/app/services/structured_note_renderers.py +0 -0
  259. {metalist-0.19.0 → metalist-0.20.0}/app/services/sync.py +0 -0
  260. {metalist-0.19.0 → metalist-0.20.0}/app/services/sync_state.py +0 -0
  261. {metalist-0.19.0 → metalist-0.20.0}/app/services/tab_state.py +0 -0
  262. {metalist-0.19.0 → metalist-0.20.0}/app/services/tag_case.py +0 -0
  263. {metalist-0.19.0 → metalist-0.20.0}/app/services/tag_details.py +0 -0
  264. {metalist-0.19.0 → metalist-0.20.0}/app/services/tag_ontology.py +0 -0
  265. {metalist-0.19.0 → metalist-0.20.0}/app/services/tag_rename.py +0 -0
  266. {metalist-0.19.0 → metalist-0.20.0}/app/services/tag_suggestion_reasons.py +0 -0
  267. {metalist-0.19.0 → metalist-0.20.0}/app/services/tag_suggestions.py +0 -0
  268. {metalist-0.19.0 → metalist-0.20.0}/app/services/tag_term_matching.py +0 -0
  269. {metalist-0.19.0 → metalist-0.20.0}/app/services/test_reset.py +0 -0
  270. {metalist-0.19.0 → metalist-0.20.0}/app/services/tokens.py +0 -0
  271. {metalist-0.19.0 → metalist-0.20.0}/app/services/update_backups.py +0 -0
  272. {metalist-0.19.0 → metalist-0.20.0}/app/services/update_installer.py +0 -0
  273. {metalist-0.19.0 → metalist-0.20.0}/app/services/update_preflight.py +0 -0
  274. {metalist-0.19.0 → metalist-0.20.0}/app/services/view_cache.py +0 -0
  275. {metalist-0.19.0 → metalist-0.20.0}/app/services/view_diff.py +0 -0
  276. {metalist-0.19.0 → metalist-0.20.0}/app/services/view_state.py +0 -0
  277. {metalist-0.19.0 → metalist-0.20.0}/app/startup_console.py +0 -0
  278. {metalist-0.19.0 → metalist-0.20.0}/app/startup_environment.py +0 -0
  279. {metalist-0.19.0 → metalist-0.20.0}/app/startup_js_sanity.py +0 -0
  280. {metalist-0.19.0 → metalist-0.20.0}/app/startup_sanity.py +0 -0
  281. {metalist-0.19.0 → metalist-0.20.0}/app/static/css/floating-notes.css +0 -0
  282. {metalist-0.19.0 → metalist-0.20.0}/app/static/css/forms.css +0 -0
  283. {metalist-0.19.0 → metalist-0.20.0}/app/static/img/favicon.ico +0 -0
  284. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/locked.js +0 -0
  285. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/main.js +0 -0
  286. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/activity-tracker.js +0 -0
  287. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/agent-menu-actions.js +0 -0
  288. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/agent-retrieval-settings.js +0 -0
  289. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/agent-web-settings.js +0 -0
  290. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/ai-agent-debug-view.js +0 -0
  291. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/ai-chat-message-placement.js +0 -0
  292. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/ai-chat-panel-controller.js +0 -0
  293. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/ai-chat-panel-service.js +0 -0
  294. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/ai-thinking-level-service.js +0 -0
  295. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/bulk-elapsed-clock.js +0 -0
  296. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/bulk-proposal-ui.js +0 -0
  297. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/bulk-scope-question.js +0 -0
  298. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/cloud-privacy-policy.js +0 -0
  299. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/confirmation-card.js +0 -0
  300. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/confirmation-question.js +0 -0
  301. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/proposal-menu.js +0 -0
  302. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/ai-chat/summary-batch-preview.js +0 -0
  303. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/api-error-diagnostics.js +0 -0
  304. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/api-failure-classification-service.js +0 -0
  305. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/app-update-service.js +0 -0
  306. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/application-state.js +0 -0
  307. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/async-result.js +0 -0
  308. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/auth.js +0 -0
  309. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/client-state-api.js +0 -0
  310. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/client-state-migration.js +0 -0
  311. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/command-palette/activation-auth-policy.js +0 -0
  312. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/command-palette/backup-command-availability.js +0 -0
  313. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
  314. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/command-palette/note-layout-preferences.js +0 -0
  315. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/command-palette/preferences-store.js +0 -0
  316. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/command-palette/tag-config-loader.js +0 -0
  317. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/command-palette/usage-store.js +0 -0
  318. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/connectivity-monitor.js +0 -0
  319. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/context-menu/context-menu-target-service.js +0 -0
  320. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/disconnect-editing-state-service.js +0 -0
  321. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/dom-utils-fixed.js +0 -0
  322. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/dom-utils.js +0 -0
  323. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/editor-commands.js +0 -0
  324. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/editor-selection.js +0 -0
  325. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/editor-toolbar.js +0 -0
  326. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/elapsed-time.js +0 -0
  327. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/error-overlay.js +0 -0
  328. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/excalidraw/excalidraw-actions.js +0 -0
  329. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/excalidraw/excalidraw-autosave.js +0 -0
  330. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/excalidraw/excalidraw-editor-service.js +0 -0
  331. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/excalidraw/excalidraw-editor-state.js +0 -0
  332. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/excalidraw/excalidraw-loader.js +0 -0
  333. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/excalidraw/excalidraw-preview-service.js +0 -0
  334. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/excalidraw/excalidraw-scene.js +0 -0
  335. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/expected-errors.js +0 -0
  336. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/location-flags.js +0 -0
  337. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/login-namespace-picker.js +0 -0
  338. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/maintenance.js +0 -0
  339. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/ai-agent-settings-modal.js +0 -0
  340. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
  341. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/backup-restore-modal.js +0 -0
  342. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/backup-result-modal.js +0 -0
  343. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
  344. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/backup-settings-modal.js +0 -0
  345. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/base-modal.js +0 -0
  346. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/confirmation-modal.js +0 -0
  347. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/delete-namespace-modal.js +0 -0
  348. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/delete-namespace-validation.js +0 -0
  349. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/dictation-settings-modal.js +0 -0
  350. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/help-modal.js +0 -0
  351. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/link-title-retry-modal.js +0 -0
  352. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/namespace-loading-page.js +0 -0
  353. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/namespace-modals.js +0 -0
  354. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/namespace-ports-profile.js +0 -0
  355. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/namespace-rename-validation.js +0 -0
  356. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/note-layout-appearance-modal.js +0 -0
  357. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/ontology-modal.js +0 -0
  358. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/password-modal.js +0 -0
  359. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/prioritize-modal.js +0 -0
  360. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/random-password-modal.js +0 -0
  361. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/reminder-modal.js +0 -0
  362. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
  363. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/search-suggestion-statistics-modal.js +0 -0
  364. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
  365. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/tag-suggestion-settings-modal.js +0 -0
  366. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/version-info-modal.js +0 -0
  367. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/modals/visual-cues-modal.js +0 -0
  368. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
  369. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
  370. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
  371. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/events/focus-events.js +0 -0
  372. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
  373. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
  374. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/events/keyboard-events.js +0 -0
  375. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
  376. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/mode-context.js +0 -0
  377. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/mode-logger.js +0 -0
  378. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
  379. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/add-style-service.js +0 -0
  380. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
  381. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/backlinks-navigation-service.js +0 -0
  382. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
  383. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
  384. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
  385. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
  386. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +0 -0
  387. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/deselect-selection-state-service.js +0 -0
  388. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/dictation-note-paste.js +0 -0
  389. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/dictation-paste-padding.js +0 -0
  390. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/dictation-paste-service.js +0 -0
  391. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/dropped-file-preview-service.js +0 -0
  392. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
  393. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
  394. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
  395. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/file-drop-policy-service.js +0 -0
  396. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
  397. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
  398. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
  399. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/floating-note-dom.js +0 -0
  400. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/floating-note-layout.js +0 -0
  401. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/floating-note-service.js +0 -0
  402. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/footnote-navigation-service.js +0 -0
  403. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/history-selection-policy-service.js +0 -0
  404. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/history-shortcut-policy-service.js +0 -0
  405. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +0 -0
  406. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +0 -0
  407. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +0 -0
  408. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
  409. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
  410. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/link-title-prefetch-service.js +0 -0
  411. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
  412. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/mermaid-render-service.js +0 -0
  413. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
  414. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
  415. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
  416. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
  417. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/note-delete-shortcut-policy-service.js +0 -0
  418. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/note-drag-indicator-service.js +0 -0
  419. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
  420. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/note-fullscreen-service.js +0 -0
  421. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
  422. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
  423. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/note-timestamp-hover-service.js +0 -0
  424. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
  425. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/paste-format-detection-service.js +0 -0
  426. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/paste-format-guess-service.js +0 -0
  427. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/paste-format-offer-service.js +0 -0
  428. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
  429. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/reference-source-navigation-service.js +0 -0
  430. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/remote-image-proxy-service.js +0 -0
  431. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/remove-formatting-service.js +0 -0
  432. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
  433. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/root-note-creation-service.js +0 -0
  434. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
  435. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
  436. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
  437. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
  438. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
  439. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
  440. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
  441. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
  442. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
  443. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
  444. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/search-suggestion-statistics-service.js +0 -0
  445. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/search-suggestion-window-labels-service.js +0 -0
  446. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/search-suggestion-windows-service.js +0 -0
  447. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +0 -0
  448. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
  449. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/selected-formatting-removal-service.js +0 -0
  450. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/selected-text-tag-service.js +0 -0
  451. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
  452. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
  453. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/tab-drag-service.js +0 -0
  454. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
  455. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -0
  456. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/tag-input-selection-service.js +0 -0
  457. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +0 -0
  458. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +0 -0
  459. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
  460. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/untagged-view-indicator-service.js +0 -0
  461. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
  462. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/note-html-sanitizer.js +0 -0
  463. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/password-generator.js +0 -0
  464. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/password-operation-response.js +0 -0
  465. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/password-policy.js +0 -0
  466. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/password-strength.js +0 -0
  467. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/reminder-store.js +0 -0
  468. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/reminder-surface-service.js +0 -0
  469. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/selection-markers.js +0 -0
  470. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/session-auth.js +0 -0
  471. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/tag-token.js +0 -0
  472. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/modules/uuid.js +0 -0
  473. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/namespace-deleted.js +0 -0
  474. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/namespace-renamed.js +0 -0
  475. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/dompurify-3.4.16.LICENSE.txt +0 -0
  476. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/dompurify-3.4.16.min.js +0 -0
  477. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/excalidraw.css +0 -0
  478. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Bold.woff2 +0 -0
  479. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Medium.woff2 +0 -0
  480. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Regular.woff2 +0 -0
  481. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-SemiBold.woff2 +0 -0
  482. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Cascadia/CascadiaCode-Regular.woff2 +0 -0
  483. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2 +0 -0
  484. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.woff2 +0 -0
  485. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.woff2 +0 -0
  486. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.woff2 +0 -0
  487. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.woff2 +0 -0
  488. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.woff2 +0 -0
  489. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-41b173a47b57366892116a575a43e2b6.woff2 +0 -0
  490. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.woff2 +0 -0
  491. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2 +0 -0
  492. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.woff2 +0 -0
  493. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.woff2 +0 -0
  494. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 +0 -0
  495. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 +0 -0
  496. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2 +0 -0
  497. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2 +0 -0
  498. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2 +0 -0
  499. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2 +0 -0
  500. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2 +0 -0
  501. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Virgil/Virgil-Regular.woff2 +0 -0
  502. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1.LICENSE.txt +0 -0
  503. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/excalidraw-0.18.1.min.js +0 -0
  504. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/markdown-it-14.3.2.LICENSE.txt +0 -0
  505. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/markdown-it-14.3.2.min.js +0 -0
  506. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/mermaid-11.16.1.LICENSE.txt +0 -0
  507. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/mermaid-11.16.1.min.js +0 -0
  508. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/zxcvbn-4.4.2.LICENSE.txt +0 -0
  509. {metalist-0.19.0 → metalist-0.20.0}/app/static/js/vendor/zxcvbn-4.4.2.js +0 -0
  510. {metalist-0.19.0 → metalist-0.20.0}/app/static/note-html-policy.json +0 -0
  511. {metalist-0.19.0 → metalist-0.20.0}/app/templates/_logo_mark.html +0 -0
  512. {metalist-0.19.0 → metalist-0.20.0}/app/templates/base.html +0 -0
  513. {metalist-0.19.0 → metalist-0.20.0}/app/templates/index.html +0 -0
  514. {metalist-0.19.0 → metalist-0.20.0}/app/templates/locked.html +0 -0
  515. {metalist-0.19.0 → metalist-0.20.0}/app/templates/maintenance.html +0 -0
  516. {metalist-0.19.0 → metalist-0.20.0}/app/templates/namespace_deleted.html +0 -0
  517. {metalist-0.19.0 → metalist-0.20.0}/app/templates/namespace_renamed.html +0 -0
  518. {metalist-0.19.0 → metalist-0.20.0}/app/templates/notes_list.html +0 -0
  519. {metalist-0.19.0 → metalist-0.20.0}/app/upload_limits.py +0 -0
  520. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/__init__.py +0 -0
  521. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/add_selected_text_tag.py +0 -0
  522. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/alphabetize_root_notes.py +0 -0
  523. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/base.py +0 -0
  524. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/bulk_tag_proposals.py +0 -0
  525. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/collapse.py +0 -0
  526. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/copy_note.py +0 -0
  527. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/create_child.py +0 -0
  528. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/create_file_preview_child.py +0 -0
  529. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/create_note.py +0 -0
  530. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/create_sibling.py +0 -0
  531. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/delete_subtree.py +0 -0
  532. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/delete_tag.py +0 -0
  533. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/expand.py +0 -0
  534. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/file_edit_session.py +0 -0
  535. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/indent.py +0 -0
  536. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/lock.py +0 -0
  537. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/move.py +0 -0
  538. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/move_to_top.py +0 -0
  539. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/outdent.py +0 -0
  540. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/paste_child.py +0 -0
  541. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/paste_sibling.py +0 -0
  542. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/prioritize.py +0 -0
  543. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/redo.py +0 -0
  544. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/rename_tag.py +0 -0
  545. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/reset_updated_at.py +0 -0
  546. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/resize_image.py +0 -0
  547. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/run_shell.py +0 -0
  548. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/search_comment_autofill.py +0 -0
  549. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/search_context_tags.py +0 -0
  550. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/set_collapse_bulk.py +0 -0
  551. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/set_collapse_in_context.py +0 -0
  552. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/split_note.py +0 -0
  553. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/toggle_reference_mode.py +0 -0
  554. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/toggle_todo_done.py +0 -0
  555. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/undo.py +0 -0
  556. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/unformat_content.py +0 -0
  557. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/update_content.py +0 -0
  558. {metalist-0.19.0 → metalist-0.20.0}/app/usecases/view.py +0 -0
  559. {metalist-0.19.0 → metalist-0.20.0}/app/utils/__init__.py +0 -0
  560. {metalist-0.19.0 → metalist-0.20.0}/app/utils/encryption.py +0 -0
  561. {metalist-0.19.0 → metalist-0.20.0}/app/utils/text_utils.py +0 -0
  562. {metalist-0.19.0 → metalist-0.20.0}/convert-from-legacy.py +0 -0
  563. {metalist-0.19.0 → metalist-0.20.0}/metalist.egg-info/dependency_links.txt +0 -0
  564. {metalist-0.19.0 → metalist-0.20.0}/metalist.egg-info/entry_points.txt +0 -0
  565. {metalist-0.19.0 → metalist-0.20.0}/metalist.egg-info/requires.txt +0 -0
  566. {metalist-0.19.0 → metalist-0.20.0}/metalist.egg-info/top_level.txt +0 -0
  567. {metalist-0.19.0 → metalist-0.20.0}/pyproject.toml +0 -0
  568. {metalist-0.19.0 → metalist-0.20.0}/scripts/generate-lan-cert.sh +0 -0
  569. {metalist-0.19.0 → metalist-0.20.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metalist
3
- Version: 0.19.0
3
+ Version: 0.20.0
4
4
  Summary: A minimalist single-user note-taking app with SSR, fast in-memory tree operations, and efficient sync.
5
5
  Project-URL: Homepage, https://github.com/evolvingstuff/metalist
6
6
  Project-URL: Repository, https://github.com/evolvingstuff/metalist
@@ -51,6 +51,17 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
51
51
  - Multi-tab search contexts with server-persisted scroll/search state (survives browser restarts)
52
52
  - Manual namespace backups/restores to a user-selected backup folder with retention controls
53
53
 
54
+ ## Changes in 0.20.0
55
+
56
+ - **Suggest Tags with AI** for the note being edited: right-click the note, or its tag bar past the tags, and choose 🤖 Suggest Tags with AI (for a note with children: This Note, or This Note and Its Children). The proposals appear under the tag bar for `+`/`−`, tags not yet in use marked **new** and grouped on the right; adding them can be undone. It shows only when an AI is set up and may see the note.
57
+ - The robot count (🤖 N) next to a note: click it to open every collapsed note on the way to its proposals and edit the highest one that has some; right-click it to Accept All or Reject All, one undo step.
58
+ - **Print**: the note menu's Print ▸ Note / View, the background menu's Print View, or the palette's Print view, through the browser's print dialog (Save as PDF included). Always light, fully expanded, without arrows, using the page's full width with its own margins, so the browser prints no header or footer.
59
+ - Export as HTML and Print open one dialog: a title with a smart default (the note's first line, or MetaList with the view's search), which heads the page unless the first note already says it and names an exported file; 🤖 Suggest with AI for the title, asked only when you press it, which never sees private notes, usernames, passwords or the search; and, when some would show, an Include Unredacted Passwords / Usernames checkbox (unchecked: each shows `••••••••`, whatever its length).
60
+ - Exported HTML files show the view as it is shown (its search, sort order, Untagged view; notes a search leaves out are absent), start fully expanded with MetaList's collapse arrows working in the file, and copy usernames, included passwords and `@copyable` values on click. Collapsing no longer shifts the page sideways.
61
+ - `@username` notes are private like `@password`: never sent to the AI. Username, password and email rows show just their icon, named on hover, and the value.
62
+ - Text never runs out of its note: a word too long for the line (a token, a hash, a long URL) breaks at the note's edge, and code, `@json`, `@diff`, CSV and Markdown code blocks wrap. Images shrink to fit their note or embedded note, and `@size` scales them only up to that width.
63
+ - The light theme's header bar is black again, with white icons and count, and the AI chat header now lines up with the search bar.
64
+
54
65
  ## Changes in 0.19.0
55
66
 
56
67
  - Paste formats: pasting a fenced code block that names its language tags the note `@markdown`, and `@markdown` now colours that code like `@code=<language>` (with its label). Code copied from VS Code is tagged `@code=<its language>`, and cells copied from Excel, Google Sheets or Numbers become CSV text tagged `@csv`.
@@ -12,6 +12,17 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
12
12
  - Multi-tab search contexts with server-persisted scroll/search state (survives browser restarts)
13
13
  - Manual namespace backups/restores to a user-selected backup folder with retention controls
14
14
 
15
+ ## Changes in 0.20.0
16
+
17
+ - **Suggest Tags with AI** for the note being edited: right-click the note, or its tag bar past the tags, and choose 🤖 Suggest Tags with AI (for a note with children: This Note, or This Note and Its Children). The proposals appear under the tag bar for `+`/`−`, tags not yet in use marked **new** and grouped on the right; adding them can be undone. It shows only when an AI is set up and may see the note.
18
+ - The robot count (🤖 N) next to a note: click it to open every collapsed note on the way to its proposals and edit the highest one that has some; right-click it to Accept All or Reject All, one undo step.
19
+ - **Print**: the note menu's Print ▸ Note / View, the background menu's Print View, or the palette's Print view, through the browser's print dialog (Save as PDF included). Always light, fully expanded, without arrows, using the page's full width with its own margins, so the browser prints no header or footer.
20
+ - Export as HTML and Print open one dialog: a title with a smart default (the note's first line, or MetaList with the view's search), which heads the page unless the first note already says it and names an exported file; 🤖 Suggest with AI for the title, asked only when you press it, which never sees private notes, usernames, passwords or the search; and, when some would show, an Include Unredacted Passwords / Usernames checkbox (unchecked: each shows `••••••••`, whatever its length).
21
+ - Exported HTML files show the view as it is shown (its search, sort order, Untagged view; notes a search leaves out are absent), start fully expanded with MetaList's collapse arrows working in the file, and copy usernames, included passwords and `@copyable` values on click. Collapsing no longer shifts the page sideways.
22
+ - `@username` notes are private like `@password`: never sent to the AI. Username, password and email rows show just their icon, named on hover, and the value.
23
+ - Text never runs out of its note: a word too long for the line (a token, a hash, a long URL) breaks at the note's edge, and code, `@json`, `@diff`, CSV and Markdown code blocks wrap. Images shrink to fit their note or embedded note, and `@size` scales them only up to that width.
24
+ - The light theme's header bar is black again, with white icons and count, and the AI chat header now lines up with the search bar.
25
+
15
26
  ## Changes in 0.19.0
16
27
 
17
28
  - Paste formats: pasting a fenced code block that names its language tags the note `@markdown`, and `@markdown` now colours that code like `@code=<language>` (with its label). Code copied from VS Code is tagged `@code=<its language>`, and cells copied from Excel, Google Sheets or Numbers become CSV text tagged `@csv`.
@@ -203,6 +203,36 @@ class AcceptTagProposalRequest(TypedDict):
203
203
  undoContext: UndoContext
204
204
  viewport: Viewport
205
205
 
206
+ @with_config(ConfigDict(strict=True))
207
+ class AddTagProposalsRequest(TypedDict):
208
+ # note id -> the AI's tags for it: the edited note and maybe notes below it.
209
+ proposals: Annotated[dict[Identifier, Annotated[list[Identifier], Field(min_length=1, max_length=12)]],
210
+ Field(min_length=1, max_length=20_000)]
211
+ clientId: Identifier
212
+ undoContext: UndoContext
213
+ viewport: Viewport
214
+
215
+ # The view a robot count (🤖 N) was shown in: its tab, search ('' for none) and kind.
216
+ @with_config(ConfigDict(strict=True))
217
+ class OpenToTagProposalsRequest(TypedDict):
218
+ search: Text
219
+ tabId: Identifier
220
+ isUntaggedView: bool
221
+ clientId: Identifier
222
+ undoContext: UndoContext
223
+ viewport: Viewport
224
+
225
+ @with_config(ConfigDict(strict=True))
226
+ class ResolveRobotProposalsRequest(TypedDict):
227
+ action: Literal["accept", "reject"]
228
+ includesHiddenChildren: bool
229
+ search: Text
230
+ tabId: Identifier
231
+ isUntaggedView: bool
232
+ clientId: Identifier
233
+ undoContext: UndoContext
234
+ viewport: Viewport
235
+
206
236
  @with_config(ConfigDict(strict=True))
207
237
  class RejectTagProposalRequest(TypedDict):
208
238
  proposal: Identifier
@@ -18,6 +18,10 @@ from app.api.transactions import transactional_route
18
18
  from app.models.utils import note_data_to_html
19
19
  from app.models.utils import render_note_data_read_only
20
20
  from app.services.ai_chat_stream import ChatTurnStream
21
+ from app.services.ai_setup import AiConnection
22
+ from app.services.ai_setup import AiNotSetUp
23
+ from app.services.ai_setup import AiProvider
24
+ from app.services.ai_setup import connect_to_ai
21
25
  from app.services.ai_chat import ai_chat_store
22
26
  from app.services.ai_chat_rendering import find_note_citation_ids
23
27
  from app.services.ai_chat_rendering import find_web_citation_ids
@@ -27,9 +31,7 @@ from app.services.agent.cloud_privacy import cloud_privacy_evaluator
27
31
  from app.services.agent.cloud_privacy import resolve_cloud_privacy_boundary
28
32
  from app.services.agent.inference import InferenceAdapter
29
33
  from app.services.agent.model_policy import SingleModelPolicy
30
- from app.services.agent.openai_inference import OPENAI_API_BASE_URL
31
34
  from app.services.agent.openai_inference import OPENAI_MODELS
32
- from app.services.agent.openai_inference import OpenAIInferenceAdapter
33
35
  from app.services.agent.openai_inference import validate_openai_model
34
36
  from app.services.agent.openai_cost_tracking import OpenAICostSnapshot
35
37
  from app.services.agent.openai_cost_tracking import openai_cost_tracker
@@ -64,6 +66,19 @@ from app.usecases.bulk_tag_proposals import apply_bulk_proposals, prepare_propos
64
66
  from app.services.bulk_operation import bulk_operation_guard
65
67
  from app.services.tab_state import tab_state_store
66
68
  from app.services.tokens import token_service
69
+ from app.services.agent.inference import InferenceProviderError
70
+ from app.services.agent.note_tag_suggestions import build_note_tag_request
71
+ from app.services.agent.export_title_suggestions import MAX_PREVIOUS_TITLES
72
+ from app.services.agent.export_title_suggestions import ExportTitleRequest
73
+ from app.services.agent.export_title_suggestions import build_export_title_request
74
+ from app.services.agent.export_title_suggestions import suggest_export_title
75
+ from app.services.agent.note_tag_suggestions import NoteTagRequest
76
+ from app.services.agent.note_tag_suggestions import suggest_note_tags
77
+ from app.services.snapshot import resolve_view_scope_membership
78
+ from app.services.html_export import resolve_export_scope
79
+ from app.services.ai_setup import describe_ai_setup
80
+ from app.services.input_errors import InputRejected
81
+ from app.utils.text_utils import strip_html
67
82
 
68
83
 
69
84
  router = APIRouter(prefix="/ai", tags=["ai"])
@@ -71,6 +86,15 @@ router = APIRouter(prefix="/ai", tags=["ai"])
71
86
  agent_context_builder = AgentContextBuilder()
72
87
 
73
88
 
89
+ def _require_ai_connection(*, provider: AiProvider, token: str, session_key: str) -> AiConnection:
90
+ # lint: allow-PY001 rationale="the user removed or never added the AI's key: an expected 409, not a defect"
91
+ try:
92
+ return connect_to_ai(provider=provider, token=token, session_key=session_key)
93
+ # lint: allow-PY001 rationale="the user removed or never added the AI's key: an expected 409, not a defect"
94
+ except AiNotSetUp as exc:
95
+ raise HTTPException(status_code=409, detail="The AI is not set up: add its API key in AI settings") from exc
96
+
97
+
74
98
  def _agent_runtime(*, inference: InferenceAdapter) -> AgentRuntime:
75
99
  return AgentRuntime(
76
100
  context_builder=agent_context_builder,
@@ -163,6 +187,76 @@ class AiChatRequest(BaseModel):
163
187
  return self
164
188
 
165
189
 
190
+ class NoteTagSuggestionAvailabilityRequest(BaseModel):
191
+ model_config = ConfigDict(extra="forbid")
192
+
193
+ provider: AiProvider
194
+ # The note as it is being edited, unsaved changes included.
195
+ tags: str = Field(..., max_length=1024 * 1024)
196
+ content_html: str = Field(..., max_length=16 * 1024 * 1024)
197
+
198
+
199
+ class NoteTagSuggestionAvailabilityResponse(BaseModel):
200
+ is_available: bool
201
+
202
+
203
+ class NoteTagSuggestionRequest(BaseModel):
204
+ model_config = ConfigDict(extra="forbid")
205
+
206
+ provider: AiProvider
207
+ model: str
208
+ thinking_level: Literal["off", "low", "medium", "high"]
209
+ # The note alone, or it and the notes below it the view shows.
210
+ include_children: bool
211
+ # The view the note is in ('' for no search).
212
+ search: str = Field(..., max_length=1024 * 1024)
213
+ tab_id: str = Field(..., min_length=1, max_length=256)
214
+ is_untagged_view: bool
215
+
216
+ @model_validator(mode="after")
217
+ def validate_model_for_provider(self) -> Self:
218
+ assert self.provider == "openai"
219
+ validate_openai_model(self.model)
220
+ return self
221
+
222
+
223
+ class ExportTitleScope(BaseModel):
224
+ model_config = ConfigDict(extra="forbid")
225
+
226
+ provider: AiProvider
227
+ # The view exported or printed, or the one its note is in ('' for no search).
228
+ search: str = Field(..., max_length=1024 * 1024)
229
+ tab_id: str = Field(..., min_length=1, max_length=256)
230
+ is_untagged_view: bool
231
+ # A note with its children ("note"), or the view as it is shown ("view", note_id '').
232
+ exported: Literal["note", "view"]
233
+ note_id: str = Field(..., max_length=256)
234
+
235
+ @model_validator(mode="after")
236
+ def validate_exported_note(self) -> Self:
237
+ if (self.exported == "note") != (self.note_id != ""):
238
+ raise ValueError("A note's export names the note; the view's export names none")
239
+ return self
240
+
241
+
242
+ class ExportTitleAvailabilityResponse(BaseModel):
243
+ # "unavailable": no AI is set up (no button); then as ExportTitleAvailability.
244
+ availability: Literal["unavailable", "nothing", "private", "partial", "ready"]
245
+
246
+
247
+ class ExportTitleSuggestionRequest(ExportTitleScope):
248
+ model: str
249
+ thinking_level: Literal["off", "low", "medium", "high"]
250
+ # Titles the AI gave in this dialog: it gives another.
251
+ previous_titles: list[str] = Field(..., max_length=MAX_PREVIOUS_TITLES)
252
+
253
+ @model_validator(mode="after")
254
+ def validate_model_for_provider(self) -> Self:
255
+ assert self.provider == "openai"
256
+ validate_openai_model(self.model)
257
+ return self
258
+
259
+
166
260
  class AiChatActivity(BaseModel):
167
261
  sequence: int = Field(..., ge=1)
168
262
  action: str
@@ -330,6 +424,178 @@ def delete_openai_credential(
330
424
  )
331
425
 
332
426
 
427
+ @router.post(
428
+ "/notes/{note_id}/tag-suggestions/availability",
429
+ response_model=NoteTagSuggestionAvailabilityResponse,
430
+ )
431
+ @transactional_route
432
+ def note_tag_suggestion_availability(
433
+ note_id: str,
434
+ payload: NoteTagSuggestionAvailabilityRequest,
435
+ response: Response,
436
+ token: Annotated[str, Depends(require_request_auth_token)],
437
+ ) -> NoteTagSuggestionAvailabilityResponse:
438
+ """Whether "Suggest Tags with AI" applies: an AI is set up and may see the note as edited."""
439
+ response.headers["Cache-Control"] = "no-store"
440
+ if not note_store.has_note(note_id):
441
+ raise HTTPException(status_code=404, detail=f"Note not found: {note_id}")
442
+ session_key = token_service.get_session_key(token)
443
+ if not describe_ai_setup(token=token, session_key=session_key).is_ready_for(payload.provider):
444
+ return NoteTagSuggestionAvailabilityResponse(is_available=False)
445
+ boundary = resolve_cloud_privacy_boundary(
446
+ preferences=load_client_preferences(token=token),
447
+ provider=payload.provider,
448
+ )
449
+ draft_effective_tags = note_store.draft_effective_tag_terms(
450
+ note_id=note_id, tags=payload.tags, plaintext=strip_html(payload.content_html),
451
+ )
452
+ reason = cloud_privacy_evaluator.draft_exclusion_reason(
453
+ note_id=note_id, draft_effective_tags=draft_effective_tags,
454
+ draft_content_html=payload.content_html, boundary=boundary,
455
+ )
456
+ return NoteTagSuggestionAvailabilityResponse(is_available=reason == "")
457
+
458
+
459
+ @router.post("/notes/{note_id}/tag-suggestions")
460
+ @transactional_route
461
+ def suggest_tags_for_note_with_ai(
462
+ note_id: str,
463
+ payload: NoteTagSuggestionRequest,
464
+ token: Annotated[str, Depends(require_request_auth_token)],
465
+ ) -> StreamingResponse:
466
+ """Ask the AI for the saved note's tags (and its children's): streams {"type": "progress"}
467
+ lines, then one {"type": "proposals"} or {"type": "failed"} line.
468
+
469
+ Nothing is written: the client adds what it gets back as proposals
470
+ (POST /notes/{id}/tag-proposals/add), one undo step.
471
+ """
472
+ if not note_store.has_note(note_id):
473
+ raise HTTPException(status_code=404, detail=f"Note not found: {note_id}")
474
+ session_key = token_service.get_session_key(token)
475
+ connection = _require_ai_connection(provider=payload.provider, token=token, session_key=session_key)
476
+ preferences = load_client_preferences(token=token)
477
+ boundary = resolve_cloud_privacy_boundary(preferences=preferences, provider=payload.provider)
478
+ if cloud_privacy_evaluator.exclusion_reason(note_id=note_id, boundary=boundary) != "":
479
+ raise InputRejected(
480
+ "Your AI privacy settings hide this note from the AI, so it can't suggest tags for it"
481
+ )
482
+ scope = resolve_view_scope_membership(
483
+ search=payload.search,
484
+ sort_mode=tab_state_store.get_sort_mode(tab_id=payload.tab_id),
485
+ is_untagged_view=payload.is_untagged_view,
486
+ )
487
+ if note_id not in scope.allowed_note_ids:
488
+ raise HTTPException(status_code=409, detail="The note is not in this view any more")
489
+ request = build_note_tag_request(
490
+ note_id=note_id, include_children=payload.include_children, boundary=boundary,
491
+ tag_skill=DEFAULT_AGENT_SKILLS.for_action("tag_proposals"), scope_note_ids=scope.allowed_note_ids,
492
+ batch_tokens=resolve_tagging_batch_tokens(preferences, payload.provider),
493
+ )
494
+ events = _note_tag_suggestion_events(
495
+ request=request, connection=connection, payload=payload,
496
+ web_settings=resolve_agent_web_settings(preferences=preferences), session_key=session_key,
497
+ )
498
+ return StreamingResponse(
499
+ events,
500
+ media_type="application/x-ndjson",
501
+ headers={"X-Accel-Buffering": "no", "Cache-Control": "no-store", "Content-Encoding": "identity"},
502
+ )
503
+
504
+
505
+ async def _note_tag_suggestion_events(
506
+ *, request: NoteTagRequest, connection: AiConnection, payload: NoteTagSuggestionRequest,
507
+ web_settings: AgentWebSettings, session_key: str,
508
+ ) -> AsyncIterator[str]:
509
+ events = suggest_note_tags(
510
+ request=request, inference=connection.inference, base_url=connection.base_url,
511
+ model=payload.model, thinking_level=payload.thinking_level,
512
+ web_settings=web_settings, session_key=session_key,
513
+ )
514
+ # lint: allow-PY001 rationale="the AI provider failed or kept answering unusably: the client shows why"
515
+ try:
516
+ async for event in events:
517
+ yield json.dumps(event) + "\n"
518
+ # lint: allow-PY001 rationale="the AI provider failed or kept answering unusably: the client shows why"
519
+ except InferenceProviderError as exc:
520
+ yield json.dumps({"type": "failed", "message": str(exc)}) + "\n"
521
+ finally:
522
+ await events.aclose()
523
+
524
+
525
+ def _export_title_request(scope: ExportTitleScope, token: str) -> ExportTitleRequest:
526
+ """What the AI may read of the export or print: the notes the AI privacy settings let it see."""
527
+ note_id = None
528
+ if scope.exported == "note":
529
+ note_id = scope.note_id
530
+ if not note_store.has_note(note_id):
531
+ raise HTTPException(status_code=404, detail=f"Note not found: {note_id}")
532
+ search = None
533
+ if scope.search != "":
534
+ search = scope.search
535
+ root_ids, allowed_note_ids = resolve_export_scope(
536
+ search=search,
537
+ sort_mode=tab_state_store.get_sort_mode(tab_id=scope.tab_id),
538
+ is_untagged_view=scope.is_untagged_view, root_note_id=note_id,
539
+ )
540
+ boundary = resolve_cloud_privacy_boundary(preferences=load_client_preferences(token=token), provider=scope.provider)
541
+ return build_export_title_request(root_ids=root_ids, allowed_note_ids=allowed_note_ids, boundary=boundary)
542
+
543
+
544
+ @router.post("/export-title/availability", response_model=ExportTitleAvailabilityResponse)
545
+ @transactional_route
546
+ def export_title_availability(
547
+ payload: ExportTitleScope,
548
+ response: Response,
549
+ token: Annotated[str, Depends(require_request_auth_token)],
550
+ ) -> ExportTitleAvailabilityResponse:
551
+ """Whether the Export and Print dialog's "Suggest with AI" can ask, and what it says about private notes."""
552
+ response.headers["Cache-Control"] = "no-store"
553
+ session_key = token_service.get_session_key(token)
554
+ if not describe_ai_setup(token=token, session_key=session_key).is_ready_for(payload.provider):
555
+ return ExportTitleAvailabilityResponse(availability="unavailable")
556
+ return ExportTitleAvailabilityResponse(availability=_export_title_request(payload, token).availability)
557
+
558
+
559
+ @router.post("/export-title")
560
+ @transactional_route
561
+ def suggest_export_title_with_ai(
562
+ payload: ExportTitleSuggestionRequest,
563
+ token: Annotated[str, Depends(require_request_auth_token)],
564
+ ) -> StreamingResponse:
565
+ """Ask the AI for an export's or print's title: one {"type": "title"} or {"type": "failed"} line.
566
+ Streamed, so Stop (the client going away) ends the request."""
567
+ session_key = token_service.get_session_key(token)
568
+ connection = _require_ai_connection(provider=payload.provider, token=token, session_key=session_key)
569
+ request = _export_title_request(payload, token)
570
+ if not request.notes:
571
+ raise InputRejected("These notes are private, so the AI can't suggest a title")
572
+ web_mode = resolve_agent_web_settings(preferences=load_client_preferences(token=token)).mode
573
+ return StreamingResponse(
574
+ _export_title_events(
575
+ request=request, payload=payload, connection=connection, web_mode=web_mode, session_key=session_key,
576
+ ),
577
+ media_type="application/x-ndjson",
578
+ headers={"X-Accel-Buffering": "no", "Cache-Control": "no-store", "Content-Encoding": "identity"},
579
+ )
580
+
581
+
582
+ async def _export_title_events(
583
+ *, request: ExportTitleRequest, payload: ExportTitleSuggestionRequest, connection: AiConnection,
584
+ web_mode: str, session_key: str,
585
+ ) -> AsyncIterator[str]:
586
+ # lint: allow-PY001 rationale="the AI provider failed or answered without a title: the dialog shows why"
587
+ try:
588
+ title = await suggest_export_title(
589
+ request=request, previous_titles=tuple(payload.previous_titles), inference=connection.inference,
590
+ base_url=connection.base_url, model=payload.model, thinking_level=payload.thinking_level,
591
+ web_mode=web_mode, session_key=session_key,
592
+ )
593
+ yield json.dumps({"type": "title", "title": title}) + "\n"
594
+ # lint: allow-PY001 rationale="the AI provider failed or answered without a title: the dialog shows why"
595
+ except InferenceProviderError as exc:
596
+ yield json.dumps({"type": "failed", "message": str(exc)}) + "\n"
597
+
598
+
333
599
  def _openai_cost_response(snapshot: OpenAICostSnapshot) -> OpenAICostResponse:
334
600
  return OpenAICostResponse(
335
601
  estimated_cost_usd=float(snapshot.estimated_cost_usd),
@@ -576,19 +842,7 @@ def stream_ai_chat(
576
842
  token: Annotated[str, Depends(require_request_auth_token)],
577
843
  ) -> StreamingResponse:
578
844
  session_key = token_service.get_session_key(token)
579
- credential_status = openai_credential_store.status(
580
- token=token,
581
- session_key=session_key,
582
- )
583
- if not credential_status.configured:
584
- raise HTTPException(
585
- status_code=409,
586
- detail="OpenAI API key is not configured",
587
- )
588
- openai_api_key = openai_credential_store.resolve(
589
- token=token,
590
- session_key=session_key,
591
- )
845
+ connection = _require_ai_connection(provider=payload.provider, token=token, session_key=session_key)
592
846
  preferences = load_client_preferences(token=token)
593
847
  prompts = DEFAULT_AGENT_PROMPTS
594
848
  skills = DEFAULT_AGENT_SKILLS
@@ -655,7 +909,7 @@ def stream_ai_chat(
655
909
  turn_stream = ChatTurnStream(store=ai_chat_store, notes=note_store,
656
910
  web_evidence_store=web_evidence_store, session_key=session_key,
657
911
  turn_id=turn_id, initial_input_tokens=initial_approx_input_tokens)
658
- events = _stream_runtime_events(payload=payload, openai_api_key=openai_api_key,
912
+ events = _stream_runtime_events(payload=payload, connection=connection,
659
913
  session_key=session_key, provider_messages=provider_messages, prompts=prompts, skills=skills,
660
914
  retrieval_settings=retrieval_settings, frozen_scope=frozen_scope, tagging_run=tagging_run,
661
915
  web_settings=web_settings, earlier_actions=earlier_actions,
@@ -673,16 +927,16 @@ def stream_ai_chat(
673
927
 
674
928
 
675
929
  async def _stream_runtime_events(
676
- *, payload: AiChatRequest, openai_api_key: str, session_key: str,
930
+ *, payload: AiChatRequest, connection: AiConnection, session_key: str,
677
931
  provider_messages: list[dict[str, str]], prompts: AgentPromptSet, skills: AgentSkillSet,
678
932
  retrieval_settings: AgentRetrievalSettings, frozen_scope: ScopedSearchSnapshot,
679
933
  tagging_run: TaggingRun, initial_input_tokens: int, web_settings: AgentWebSettings,
680
934
  earlier_actions: list[dict[str, object]],
681
935
  ) -> AsyncIterator[dict[str, object]]:
682
- action, start_label = 'provider_runtime', 'Connecting to OpenAI API'
683
- ready_label = 'OpenAI API ready · 1,050,000-token context'
684
- base_url = OPENAI_API_BASE_URL
685
- runtime = _agent_runtime(inference=OpenAIInferenceAdapter(api_key=openai_api_key, cost_tracker=openai_cost_tracker))
936
+ action, start_label = 'provider_runtime', f'Connecting to {connection.label} API'
937
+ ready_label = connection.ready_label
938
+ base_url = connection.base_url
939
+ runtime = _agent_runtime(inference=connection.inference)
686
940
  yield dict(type='action_status', action=action, status='started', label=start_label,
687
941
  approx_input_tokens=initial_input_tokens, output_tokens_received=0, duration_ms=0.0)
688
942
  yield dict(type='action_status', action=action, status='completed', label=ready_label,
@@ -31,7 +31,10 @@ from app.api.note_requests import (
31
31
  SaveNoteRequest,
32
32
  AddSelectedTextTagRequest,
33
33
  AcceptTagProposalRequest,
34
+ AddTagProposalsRequest,
35
+ OpenToTagProposalsRequest,
34
36
  RejectTagProposalRequest,
37
+ ResolveRobotProposalsRequest,
35
38
  SplitNoteRequest,
36
39
  ToggleTodoDoneRequest,
37
40
  UnformatNoteContentRequest,
@@ -60,6 +63,7 @@ from app.api.transactions import transactional_route
60
63
  from app.services.sync import touch_client
61
64
  from app.services.snapshot import build_view_state
62
65
  from app.services.snapshot import resolve_search_scope
66
+ from app.services.snapshot import resolve_view_scope_membership
63
67
  from app.services.note_store import store as note_store
64
68
  from app.services.exception_capture import CapturedExceptionContext
65
69
  from app.usecases.create_note import CmdCreateNote
@@ -78,7 +82,10 @@ from app.services.link_title_retry import (
78
82
  from app.usecases.add_selected_text_tag import CmdAddSelectedTextTag
79
83
  from app.usecases.tag_proposals import (
80
84
  CmdAcceptTagProposal,
85
+ CmdAddTagProposals,
86
+ CmdOpenToTagProposals,
81
87
  CmdRejectTagProposal,
88
+ CmdResolveRobotProposals,
82
89
  )
83
90
  from app.usecases.delete_subtree import CmdDeleteSubtree
84
91
  from app.usecases.move import CmdMove
@@ -132,7 +139,10 @@ from app.services.search_history import (
132
139
  from app.services.root_sorting import is_root_reorder_locked
133
140
  from app.services.root_sorting import normalize_sort_mode
134
141
  from app.services.search_query import parse_search_query
142
+ from app.services.html_export import EXPORT_PURPOSES
135
143
  from app.services.html_export import build_notes_export_document
144
+ from app.services.html_export import default_export_title
145
+ from app.services.html_export import exported_credential_counts
136
146
  from app.services.html_export import build_notes_export_filename
137
147
  from app.services.note_fullscreen import build_note_fullscreen_markup
138
148
  from app.services.link_titles import link_title_store
@@ -682,6 +692,35 @@ def search_tag_details(request: Request, payload: SearchTagDetailsRequest) -> Di
682
692
  return {"lines": list(lines)}
683
693
 
684
694
 
695
+ @router.get("/notes/export-summary")
696
+ def export_summary(request: Request) -> Dict[str, object]:
697
+ """What an export or print of a note (with its children) or of the view holds,
698
+ for its dialog: the title it starts with, and how many passwords and usernames it shows."""
699
+ _require_bearer_token(request)
700
+ search_query = request.query_params.get("search_query")
701
+ tab_id = request.query_params.get("tab_id")
702
+ is_untagged_view = request.query_params.get("is_untagged_view")
703
+ if search_query is None or tab_id is None or tab_id == "" or is_untagged_view not in {"true", "false"}:
704
+ raise HTTPException(status_code=400, detail="search_query, tab_id and is_untagged_view are required")
705
+ note_id = request.query_params.get("note_id")
706
+ if note_id is not None:
707
+ _require_note_present(note_id, context="notes.export-summary")
708
+ normalized_search = search_query
709
+ if normalized_search == "":
710
+ normalized_search = None
711
+ counts = exported_credential_counts(
712
+ search=normalized_search, sort_mode=_resolve_tab_sort_mode(tab_id),
713
+ is_untagged_view=is_untagged_view == "true", root_note_id=note_id,
714
+ )
715
+ return {
716
+ "title": default_export_title(
717
+ search=search_query, is_untagged_view=is_untagged_view == "true", root_note_id=note_id,
718
+ ),
719
+ "password_count": counts.passwords,
720
+ "username_count": counts.usernames,
721
+ }
722
+
723
+
685
724
  @router.get("/notes/export-html")
686
725
  def export_notes_html(request: Request) -> Response:
687
726
  token = _require_bearer_token(request)
@@ -709,14 +748,40 @@ def export_notes_html(request: Request) -> Response:
709
748
  normalized_theme = theme.strip().lower()
710
749
  if normalized_theme not in {"dark", "light"}:
711
750
  raise HTTPException(status_code=400, detail="theme must be 'light' or 'dark'")
751
+ # The view as the tab shows it: its sort order and kind (All or Untagged notes).
752
+ tab_id = request.query_params.get("tab_id")
753
+ if tab_id is None or tab_id == "":
754
+ raise HTTPException(status_code=400, detail="tab_id query parameter is required")
755
+ is_untagged_view = request.query_params.get("is_untagged_view")
756
+ if is_untagged_view not in {"true", "false"}:
757
+ raise HTTPException(status_code=400, detail="is_untagged_view query parameter must be 'true' or 'false'")
758
+
759
+ # Both carry the user's title; a download collapses and expands, a print is always light.
760
+ purpose = request.query_params.get("purpose")
761
+ if purpose not in EXPORT_PURPOSES:
762
+ raise HTTPException(status_code=400, detail="purpose query parameter must be 'download' or 'print'")
763
+ title = request.query_params.get("title")
764
+ if title is None:
765
+ raise HTTPException(status_code=400, detail="title query parameter is required")
766
+ if purpose == "print":
767
+ normalized_theme = "light"
768
+ # Passwords and usernames show only when the user chose to include them in this export.
769
+ include_credentials = request.query_params.get("include_credentials")
770
+ if include_credentials not in {"true", "false"}:
771
+ raise HTTPException(status_code=400, detail="include_credentials query parameter must be 'true' or 'false'")
712
772
 
713
773
  document = build_notes_export_document(
714
774
  search=normalized_search,
775
+ sort_mode=_resolve_tab_sort_mode(tab_id),
776
+ is_untagged_view=is_untagged_view == "true",
715
777
  theme=normalized_theme,
716
778
  token=token,
717
779
  root_note_id=note_id,
780
+ purpose=purpose,
781
+ title=title,
782
+ include_credentials=include_credentials == "true",
718
783
  )
719
- filename = build_notes_export_filename()
784
+ filename = build_notes_export_filename(title)
720
785
  quoted_filename = urllib.parse.quote(filename)
721
786
  return Response(
722
787
  content=document,
@@ -967,6 +1032,67 @@ def accept_tag_proposal(request: Request, note_id: str, body: AcceptTagProposalR
967
1032
  )
968
1033
 
969
1034
 
1035
+ @router.post("/notes/{note_id}/tag-proposals/add")
1036
+ @transactional_route
1037
+ def add_tag_proposals(request: Request, note_id: str, body: AddTagProposalsRequest):
1038
+ token = _require_bearer_token(request)
1039
+ viewport = _require_viewport(body)
1040
+ _require_note_present(note_id, context="notes.tag-proposals.add")
1041
+ return _execute_tag_proposal_mutation(
1042
+ command=CmdAddTagProposals(
1043
+ note_id=note_id,
1044
+ proposals={target_id: tuple(tags) for target_id, tags in body["proposals"].items()},
1045
+ token=token,
1046
+ client_id=body["clientId"],
1047
+ undo_context=body["undoContext"],
1048
+ viewport=viewport,
1049
+ )
1050
+ )
1051
+
1052
+
1053
+ def _robot_scope_note_ids(body: dict) -> frozenset[str]:
1054
+ """The notes the view showing a robot count lets it count (as /notes/view resolves them)."""
1055
+ return resolve_view_scope_membership(
1056
+ search=body["search"],
1057
+ sort_mode=_resolve_tab_sort_mode(body["tabId"]),
1058
+ is_untagged_view=body["isUntaggedView"],
1059
+ ).allowed_note_ids
1060
+
1061
+
1062
+ @router.post("/notes/{note_id}/tag-proposals/open")
1063
+ @transactional_route
1064
+ def open_to_tag_proposals(note_id: str, body: OpenToTagProposalsRequest):
1065
+ viewport = _require_viewport(body)
1066
+ _require_note_present(note_id, context="notes.tag-proposals.open")
1067
+ return CmdOpenToTagProposals(
1068
+ note_id=note_id,
1069
+ scope_note_ids=_robot_scope_note_ids(body),
1070
+ client_id=body["clientId"],
1071
+ undo_context=body["undoContext"],
1072
+ viewport=viewport,
1073
+ ).execute()
1074
+
1075
+
1076
+ @router.post("/notes/{note_id}/tag-proposals/resolve-all")
1077
+ @transactional_route
1078
+ def resolve_robot_tag_proposals(request: Request, note_id: str, body: ResolveRobotProposalsRequest):
1079
+ token = _require_bearer_token(request)
1080
+ viewport = _require_viewport(body)
1081
+ _require_note_present(note_id, context="notes.tag-proposals.resolve-all")
1082
+ return _execute_tag_proposal_mutation(
1083
+ command=CmdResolveRobotProposals(
1084
+ note_id=note_id,
1085
+ action=body["action"],
1086
+ includes_hidden_children=body["includesHiddenChildren"],
1087
+ scope_note_ids=_robot_scope_note_ids(body),
1088
+ token=token,
1089
+ client_id=body["clientId"],
1090
+ undo_context=body["undoContext"],
1091
+ viewport=viewport,
1092
+ )
1093
+ )
1094
+
1095
+
970
1096
  @router.post("/notes/{note_id}/tag-proposals/reject")
971
1097
  @transactional_route
972
1098
  def reject_tag_proposal(request: Request, note_id: str, body: RejectTagProposalRequest):
@@ -171,6 +171,7 @@ CAPTURE_BOUNDARIES = {
171
171
  # Handle expected external I/O, provider, image or LaTeX parsing failure.
172
172
  'convert-from-legacy.py:_legacy_rule_is_currently_valid:parse_capture': ('OntologyParseError',),
173
173
  # Handle expected external I/O, provider, image or LaTeX parsing failure.
174
+ 'main.py:_bind_https_proxy_server:bind_capture': ('OSError',),
174
175
  'main.py:_is_process_running:kill_capture': ('ProcessLookupError', 'PermissionError'),
175
176
  # Handle expected external I/O, provider, image or LaTeX parsing failure.
176
177
  'main.py:_send_signal_if_running:signal_capture': ('ProcessLookupError',),
@@ -50,7 +50,7 @@ def _format_note_content_standard(*, content_html: str, tags: str) -> str:
50
50
  return format_note_content_for_view(
51
51
  content_html=content_html,
52
52
  tags=tags,
53
- redact_passwords=False,
53
+ redact_credentials=False,
54
54
  )
55
55
 
56
56