MetaList 0.17.0__tar.gz → 0.18.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (556) hide show
  1. {metalist-0.17.0 → metalist-0.18.0}/PKG-INFO +6 -1
  2. {metalist-0.17.0 → metalist-0.18.0}/README.md +5 -0
  3. {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/notes.py +19 -3
  4. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-notes.md +2 -2
  5. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-releases.md +5 -0
  6. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-search.md +2 -0
  7. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-tags.md +2 -0
  8. {metalist-0.17.0 → metalist-0.18.0}/app/services/search_history.py +3 -0
  9. {metalist-0.17.0 → metalist-0.18.0}/app/services/search_index.py +109 -0
  10. metalist-0.18.0/app/services/tag_suggestion_reasons.py +326 -0
  11. {metalist-0.17.0 → metalist-0.18.0}/app/services/tag_suggestions.py +182 -52
  12. {metalist-0.17.0 → metalist-0.18.0}/app/services/tag_term_matching.py +51 -0
  13. {metalist-0.17.0 → metalist-0.18.0}/app/static/css/main.css +17 -0
  14. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/context-menu/context-menu-registry.js +16 -7
  15. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/context-menu/context-menu-service.js +43 -2
  16. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/context-menu-events.js +8 -5
  17. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +14 -0
  18. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +44 -7
  19. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +32 -4
  20. {metalist-0.17.0 → metalist-0.18.0}/app/version.py +1 -1
  21. {metalist-0.17.0 → metalist-0.18.0}/metalist.egg-info/PKG-INFO +6 -1
  22. {metalist-0.17.0 → metalist-0.18.0}/metalist.egg-info/SOURCES.txt +1 -0
  23. {metalist-0.17.0 → metalist-0.18.0}/MANIFEST.in +0 -0
  24. {metalist-0.17.0 → metalist-0.18.0}/app/__init__.py +0 -0
  25. {metalist-0.17.0 → metalist-0.18.0}/app/api/__init__.py +0 -0
  26. {metalist-0.17.0 → metalist-0.18.0}/app/api/deps.py +0 -0
  27. {metalist-0.17.0 → metalist-0.18.0}/app/api/dev.py +0 -0
  28. {metalist-0.17.0 → metalist-0.18.0}/app/api/middleware/auth.py +0 -0
  29. {metalist-0.17.0 → metalist-0.18.0}/app/api/note_requests.py +0 -0
  30. {metalist-0.17.0 → metalist-0.18.0}/app/api/request_auth.py +0 -0
  31. {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/ai.py +0 -0
  32. {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/app_updates.py +0 -0
  33. {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/auth.py +0 -0
  34. {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/backups.py +0 -0
  35. {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/files.py +0 -0
  36. {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/ontology.py +0 -0
  37. {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/reminders.py +0 -0
  38. {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/remote_images.py +0 -0
  39. {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/test.py +0 -0
  40. {metalist-0.17.0 → metalist-0.18.0}/app/api/transactions.py +0 -0
  41. {metalist-0.17.0 → metalist-0.18.0}/app/api/upload_limits.py +0 -0
  42. {metalist-0.17.0 → metalist-0.18.0}/app/config.py +0 -0
  43. {metalist-0.17.0 → metalist-0.18.0}/app/data_directory.py +0 -0
  44. {metalist-0.17.0 → metalist-0.18.0}/app/db/__init__.py +0 -0
  45. {metalist-0.17.0 → metalist-0.18.0}/app/db/backup_settings_sql.py +0 -0
  46. {metalist-0.17.0 → metalist-0.18.0}/app/db/editable_files_migration.py +0 -0
  47. {metalist-0.17.0 → metalist-0.18.0}/app/db/engine.py +0 -0
  48. {metalist-0.17.0 → metalist-0.18.0}/app/db/file_schema.py +0 -0
  49. {metalist-0.17.0 → metalist-0.18.0}/app/db/file_session.py +0 -0
  50. {metalist-0.17.0 → metalist-0.18.0}/app/db/files_sql.py +0 -0
  51. {metalist-0.17.0 → metalist-0.18.0}/app/db/link_titles_sql.py +0 -0
  52. {metalist-0.17.0 → metalist-0.18.0}/app/db/live_recovery.py +0 -0
  53. {metalist-0.17.0 → metalist-0.18.0}/app/db/migrations.py +0 -0
  54. {metalist-0.17.0 → metalist-0.18.0}/app/db/notes_sql.py +0 -0
  55. {metalist-0.17.0 → metalist-0.18.0}/app/db/ontology_rules_sql.py +0 -0
  56. {metalist-0.17.0 → metalist-0.18.0}/app/db/reminders_sql.py +0 -0
  57. {metalist-0.17.0 → metalist-0.18.0}/app/db/retire_sounds.py +0 -0
  58. {metalist-0.17.0 → metalist-0.18.0}/app/db/schema.py +0 -0
  59. {metalist-0.17.0 → metalist-0.18.0}/app/db/search_history_sql.py +0 -0
  60. {metalist-0.17.0 → metalist-0.18.0}/app/db/session.py +0 -0
  61. {metalist-0.17.0 → metalist-0.18.0}/app/db/settings_sql.py +0 -0
  62. {metalist-0.17.0 → metalist-0.18.0}/app/db/tab_state_sql.py +0 -0
  63. {metalist-0.17.0 → metalist-0.18.0}/app/db/version.py +0 -0
  64. {metalist-0.17.0 → metalist-0.18.0}/app/encryption_audit.py +0 -0
  65. {metalist-0.17.0 → metalist-0.18.0}/app/exception_boundaries.py +0 -0
  66. {metalist-0.17.0 → metalist-0.18.0}/app/https_proxy.py +0 -0
  67. {metalist-0.17.0 → metalist-0.18.0}/app/main.py +0 -0
  68. {metalist-0.17.0 → metalist-0.18.0}/app/models/commands.py +0 -0
  69. {metalist-0.17.0 → metalist-0.18.0}/app/models/database.py +0 -0
  70. {metalist-0.17.0 → metalist-0.18.0}/app/models/entities.py +0 -0
  71. {metalist-0.17.0 → metalist-0.18.0}/app/models/enums.py +0 -0
  72. {metalist-0.17.0 → metalist-0.18.0}/app/models/linked_list.py +0 -0
  73. {metalist-0.17.0 → metalist-0.18.0}/app/models/list_operations.py +0 -0
  74. {metalist-0.17.0 → metalist-0.18.0}/app/models/list_traversal.py +0 -0
  75. {metalist-0.17.0 → metalist-0.18.0}/app/models/note_crud.py +0 -0
  76. {metalist-0.17.0 → metalist-0.18.0}/app/models/utils.py +0 -0
  77. {metalist-0.17.0 → metalist-0.18.0}/app/network_settings.py +0 -0
  78. {metalist-0.17.0 → metalist-0.18.0}/app/presentation/__init__.py +0 -0
  79. {metalist-0.17.0 → metalist-0.18.0}/app/presentation/login_page.py +0 -0
  80. {metalist-0.17.0 → metalist-0.18.0}/app/presentation/render/__init__.py +0 -0
  81. {metalist-0.17.0 → metalist-0.18.0}/app/presentation/render/note_renderer.py +0 -0
  82. {metalist-0.17.0 → metalist-0.18.0}/app/presentation/templates.py +0 -0
  83. {metalist-0.17.0 → metalist-0.18.0}/app/security/authenticated_logging.py +0 -0
  84. {metalist-0.17.0 → metalist-0.18.0}/app/security/encryption.py +0 -0
  85. {metalist-0.17.0 → metalist-0.18.0}/app/security/http_error_diagnostics.py +0 -0
  86. {metalist-0.17.0 → metalist-0.18.0}/app/security/http_headers.py +0 -0
  87. {metalist-0.17.0 → metalist-0.18.0}/app/security/note_html.py +0 -0
  88. {metalist-0.17.0 → metalist-0.18.0}/app/security/request_boundary.py +0 -0
  89. {metalist-0.17.0 → metalist-0.18.0}/app/security/request_hosts.py +0 -0
  90. {metalist-0.17.0 → metalist-0.18.0}/app/security/sensitive_cache.py +0 -0
  91. {metalist-0.17.0 → metalist-0.18.0}/app/security/sensitive_logging.py +0 -0
  92. {metalist-0.17.0 → metalist-0.18.0}/app/security/shell_execution.py +0 -0
  93. {metalist-0.17.0 → metalist-0.18.0}/app/security/validation_errors.py +0 -0
  94. {metalist-0.17.0 → metalist-0.18.0}/app/server_runtime.py +0 -0
  95. {metalist-0.17.0 → metalist-0.18.0}/app/services/__init__.py +0 -0
  96. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/__init__.py +0 -0
  97. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/action_log.py +0 -0
  98. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/agent_loop.py +0 -0
  99. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/agent_tools.py +0 -0
  100. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/citation_tokens.py +0 -0
  101. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/cloud_privacy.py +0 -0
  102. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/context.py +0 -0
  103. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/evidence_serialization.py +0 -0
  104. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/execution_errors.py +0 -0
  105. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/failure_explanations.py +0 -0
  106. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/help_catalog.py +0 -0
  107. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/history.py +0 -0
  108. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/inference.py +0 -0
  109. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/investigation.py +0 -0
  110. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/judging.py +0 -0
  111. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/menu_actions.py +0 -0
  112. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/model_policy.py +0 -0
  113. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/note_aliases.py +0 -0
  114. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/openai_cost_tracking.py +0 -0
  115. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/openai_inference.py +0 -0
  116. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/prompt_settings.py +0 -0
  117. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/prompts/__init__.py +0 -0
  118. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/prompts/agent.md +0 -0
  119. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/prompts/final-response.md +0 -0
  120. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/prompts/system.md +0 -0
  121. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/release_notes.py +0 -0
  122. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/retrieval_settings.py +0 -0
  123. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/runtime.py +0 -0
  124. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/scope.py +0 -0
  125. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skill_settings.py +0 -0
  126. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/__init__.py +0 -0
  127. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-ai.md +0 -0
  128. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-data.md +0 -0
  129. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-dictation.md +0 -0
  130. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-formatting.md +0 -0
  131. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-menus.md +0 -0
  132. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-privacy.md +0 -0
  133. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-references.md +0 -0
  134. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-reminders.md +0 -0
  135. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/staged-summary.md +0 -0
  136. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/tag-proposals.md +0 -0
  137. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/web-browsing.md +0 -0
  138. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/staged_summary.py +0 -0
  139. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/strict_structured_outputs.py +0 -0
  140. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/structured_inference.py +0 -0
  141. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/summary_batch_store.py +0 -0
  142. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/tagging.py +0 -0
  143. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/tagging_run.py +0 -0
  144. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/token_estimation.py +0 -0
  145. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/tool_calling.py +0 -0
  146. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/trace.py +0 -0
  147. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/web_actions.py +0 -0
  148. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/web_capabilities.py +0 -0
  149. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/web_evidence.py +0 -0
  150. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/web_fetch.py +0 -0
  151. {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/web_settings.py +0 -0
  152. {metalist-0.17.0 → metalist-0.18.0}/app/services/ai_chat.py +0 -0
  153. {metalist-0.17.0 → metalist-0.18.0}/app/services/ai_chat_rendering.py +0 -0
  154. {metalist-0.17.0 → metalist-0.18.0}/app/services/ai_chat_stream.py +0 -0
  155. {metalist-0.17.0 → metalist-0.18.0}/app/services/app_update_worker.py +0 -0
  156. {metalist-0.17.0 → metalist-0.18.0}/app/services/app_updates.py +0 -0
  157. {metalist-0.17.0 → metalist-0.18.0}/app/services/auth.py +0 -0
  158. {metalist-0.17.0 → metalist-0.18.0}/app/services/auth_cache_state.py +0 -0
  159. {metalist-0.17.0 → metalist-0.18.0}/app/services/auth_service.py +0 -0
  160. {metalist-0.17.0 → metalist-0.18.0}/app/services/backlink_index.py +0 -0
  161. {metalist-0.17.0 → metalist-0.18.0}/app/services/backlinks.py +0 -0
  162. {metalist-0.17.0 → metalist-0.18.0}/app/services/backup_service.py +0 -0
  163. {metalist-0.17.0 → metalist-0.18.0}/app/services/backup_settings_service.py +0 -0
  164. {metalist-0.17.0 → metalist-0.18.0}/app/services/bulk_operation.py +0 -0
  165. {metalist-0.17.0 → metalist-0.18.0}/app/services/chess_rendering.py +0 -0
  166. {metalist-0.17.0 → metalist-0.18.0}/app/services/client_state_service.py +0 -0
  167. {metalist-0.17.0 → metalist-0.18.0}/app/services/code_rendering.py +0 -0
  168. {metalist-0.17.0 → metalist-0.18.0}/app/services/content_cache.py +0 -0
  169. {metalist-0.17.0 → metalist-0.18.0}/app/services/content_formatting.py +0 -0
  170. {metalist-0.17.0 → metalist-0.18.0}/app/services/diagnostics.py +0 -0
  171. {metalist-0.17.0 → metalist-0.18.0}/app/services/dictation_cleanup.py +0 -0
  172. {metalist-0.17.0 → metalist-0.18.0}/app/services/diff_rendering.py +0 -0
  173. {metalist-0.17.0 → metalist-0.18.0}/app/services/embedded_references.py +0 -0
  174. {metalist-0.17.0 → metalist-0.18.0}/app/services/encryption.py +0 -0
  175. {metalist-0.17.0 → metalist-0.18.0}/app/services/exception_capture.py +0 -0
  176. {metalist-0.17.0 → metalist-0.18.0}/app/services/file_edit_sessions.py +0 -0
  177. {metalist-0.17.0 → metalist-0.18.0}/app/services/file_registry.py +0 -0
  178. {metalist-0.17.0 → metalist-0.18.0}/app/services/file_storage.py +0 -0
  179. {metalist-0.17.0 → metalist-0.18.0}/app/services/file_text_preview.py +0 -0
  180. {metalist-0.17.0 → metalist-0.18.0}/app/services/footnote_layout.py +0 -0
  181. {metalist-0.17.0 → metalist-0.18.0}/app/services/footnote_rendering.py +0 -0
  182. {metalist-0.17.0 → metalist-0.18.0}/app/services/hierarchy.py +0 -0
  183. {metalist-0.17.0 → metalist-0.18.0}/app/services/html_export.py +0 -0
  184. {metalist-0.17.0 → metalist-0.18.0}/app/services/html_unformatting.py +0 -0
  185. {metalist-0.17.0 → metalist-0.18.0}/app/services/hydration_state.py +0 -0
  186. {metalist-0.17.0 → metalist-0.18.0}/app/services/image_size_formatting.py +0 -0
  187. {metalist-0.17.0 → metalist-0.18.0}/app/services/inline_image_dimensions.py +0 -0
  188. {metalist-0.17.0 → metalist-0.18.0}/app/services/inline_image_occurrences.py +0 -0
  189. {metalist-0.17.0 → metalist-0.18.0}/app/services/input_errors.py +0 -0
  190. {metalist-0.17.0 → metalist-0.18.0}/app/services/integrity.py +0 -0
  191. {metalist-0.17.0 → metalist-0.18.0}/app/services/latex_rendering.py +0 -0
  192. {metalist-0.17.0 → metalist-0.18.0}/app/services/link_title_retry.py +0 -0
  193. {metalist-0.17.0 → metalist-0.18.0}/app/services/link_titles.py +0 -0
  194. {metalist-0.17.0 → metalist-0.18.0}/app/services/login_rate_limit.py +0 -0
  195. {metalist-0.17.0 → metalist-0.18.0}/app/services/maintenance_mode.py +0 -0
  196. {metalist-0.17.0 → metalist-0.18.0}/app/services/markdown_rendering.py +0 -0
  197. {metalist-0.17.0 → metalist-0.18.0}/app/services/mermaid_note_rendering.py +0 -0
  198. {metalist-0.17.0 → metalist-0.18.0}/app/services/namespace_deletion_jobs.py +0 -0
  199. {metalist-0.17.0 → metalist-0.18.0}/app/services/namespace_deletion_worker.py +0 -0
  200. {metalist-0.17.0 → metalist-0.18.0}/app/services/namespace_rename_jobs.py +0 -0
  201. {metalist-0.17.0 → metalist-0.18.0}/app/services/namespace_rename_worker.py +0 -0
  202. {metalist-0.17.0 → metalist-0.18.0}/app/services/namespace_runtime_guard.py +0 -0
  203. {metalist-0.17.0 → metalist-0.18.0}/app/services/namespace_switcher.py +0 -0
  204. {metalist-0.17.0 → metalist-0.18.0}/app/services/note_fullscreen.py +0 -0
  205. {metalist-0.17.0 → metalist-0.18.0}/app/services/note_image_tags.py +0 -0
  206. {metalist-0.17.0 → metalist-0.18.0}/app/services/note_ordering.py +0 -0
  207. {metalist-0.17.0 → metalist-0.18.0}/app/services/note_store.py +0 -0
  208. {metalist-0.17.0 → metalist-0.18.0}/app/services/ontology_rules_store.py +0 -0
  209. {metalist-0.17.0 → metalist-0.18.0}/app/services/openai_credentials.py +0 -0
  210. {metalist-0.17.0 → metalist-0.18.0}/app/services/password_note_fields.py +0 -0
  211. {metalist-0.17.0 → metalist-0.18.0}/app/services/public_http.py +0 -0
  212. {metalist-0.17.0 → metalist-0.18.0}/app/services/reference_presentation.py +0 -0
  213. {metalist-0.17.0 → metalist-0.18.0}/app/services/reminders.py +0 -0
  214. {metalist-0.17.0 → metalist-0.18.0}/app/services/remote_image_proxy.py +0 -0
  215. {metalist-0.17.0 → metalist-0.18.0}/app/services/request_recovery.py +0 -0
  216. {metalist-0.17.0 → metalist-0.18.0}/app/services/resource_limits.py +0 -0
  217. {metalist-0.17.0 → metalist-0.18.0}/app/services/root_sorting.py +0 -0
  218. {metalist-0.17.0 → metalist-0.18.0}/app/services/runtime_generation.py +0 -0
  219. {metalist-0.17.0 → metalist-0.18.0}/app/services/runtime_hardening.py +0 -0
  220. {metalist-0.17.0 → metalist-0.18.0}/app/services/runtime_lock.py +0 -0
  221. {metalist-0.17.0 → metalist-0.18.0}/app/services/search_history_storage.py +0 -0
  222. {metalist-0.17.0 → metalist-0.18.0}/app/services/search_query.py +0 -0
  223. {metalist-0.17.0 → metalist-0.18.0}/app/services/search_text.py +0 -0
  224. {metalist-0.17.0 → metalist-0.18.0}/app/services/selected_text_tag.py +0 -0
  225. {metalist-0.17.0 → metalist-0.18.0}/app/services/self_update.py +0 -0
  226. {metalist-0.17.0 → metalist-0.18.0}/app/services/session_timeout_service.py +0 -0
  227. {metalist-0.17.0 → metalist-0.18.0}/app/services/shell_session_service.py +0 -0
  228. {metalist-0.17.0 → metalist-0.18.0}/app/services/snapshot.py +0 -0
  229. {metalist-0.17.0 → metalist-0.18.0}/app/services/store.py +0 -0
  230. {metalist-0.17.0 → metalist-0.18.0}/app/services/structured_note_renderers.py +0 -0
  231. {metalist-0.17.0 → metalist-0.18.0}/app/services/sync.py +0 -0
  232. {metalist-0.17.0 → metalist-0.18.0}/app/services/sync_state.py +0 -0
  233. {metalist-0.17.0 → metalist-0.18.0}/app/services/tab_state.py +0 -0
  234. {metalist-0.17.0 → metalist-0.18.0}/app/services/tag_case.py +0 -0
  235. {metalist-0.17.0 → metalist-0.18.0}/app/services/tag_ontology.py +0 -0
  236. {metalist-0.17.0 → metalist-0.18.0}/app/services/tag_rename.py +0 -0
  237. {metalist-0.17.0 → metalist-0.18.0}/app/services/test_reset.py +0 -0
  238. {metalist-0.17.0 → metalist-0.18.0}/app/services/tokens.py +0 -0
  239. {metalist-0.17.0 → metalist-0.18.0}/app/services/undo_state.py +0 -0
  240. {metalist-0.17.0 → metalist-0.18.0}/app/services/update_backups.py +0 -0
  241. {metalist-0.17.0 → metalist-0.18.0}/app/services/update_installer.py +0 -0
  242. {metalist-0.17.0 → metalist-0.18.0}/app/services/update_preflight.py +0 -0
  243. {metalist-0.17.0 → metalist-0.18.0}/app/services/view_cache.py +0 -0
  244. {metalist-0.17.0 → metalist-0.18.0}/app/services/view_diff.py +0 -0
  245. {metalist-0.17.0 → metalist-0.18.0}/app/services/view_state.py +0 -0
  246. {metalist-0.17.0 → metalist-0.18.0}/app/services/windows_process_control.py +0 -0
  247. {metalist-0.17.0 → metalist-0.18.0}/app/startup_console.py +0 -0
  248. {metalist-0.17.0 → metalist-0.18.0}/app/startup_environment.py +0 -0
  249. {metalist-0.17.0 → metalist-0.18.0}/app/startup_js_sanity.py +0 -0
  250. {metalist-0.17.0 → metalist-0.18.0}/app/startup_sanity.py +0 -0
  251. {metalist-0.17.0 → metalist-0.18.0}/app/startup_sanity_config.py +0 -0
  252. {metalist-0.17.0 → metalist-0.18.0}/app/static/config/agent-menu-actions.json +0 -0
  253. {metalist-0.17.0 → metalist-0.18.0}/app/static/config/command_palette_tags.json +0 -0
  254. {metalist-0.17.0 → metalist-0.18.0}/app/static/css/floating-notes.css +0 -0
  255. {metalist-0.17.0 → metalist-0.18.0}/app/static/css/forms.css +0 -0
  256. {metalist-0.17.0 → metalist-0.18.0}/app/static/img/favicon.ico +0 -0
  257. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/locked.js +0 -0
  258. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/main.js +0 -0
  259. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/activity-tracker.js +0 -0
  260. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/agent-menu-actions.js +0 -0
  261. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/agent-retrieval-settings.js +0 -0
  262. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/agent-web-settings.js +0 -0
  263. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/ai-agent-debug-view.js +0 -0
  264. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/ai-chat-api.js +0 -0
  265. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/ai-chat-message-placement.js +0 -0
  266. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/ai-chat-panel-controller.js +0 -0
  267. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/ai-chat-panel-service.js +0 -0
  268. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/ai-thinking-level-service.js +0 -0
  269. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/bulk-elapsed-clock.js +0 -0
  270. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/bulk-proposal-ui.js +0 -0
  271. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/bulk-scope-question.js +0 -0
  272. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/cloud-privacy-policy.js +0 -0
  273. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/confirmation-card.js +0 -0
  274. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/confirmation-question.js +0 -0
  275. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/proposal-menu.js +0 -0
  276. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/summary-batch-preview.js +0 -0
  277. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/api-client.js +0 -0
  278. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/api-error-diagnostics.js +0 -0
  279. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/api-failure-classification-service.js +0 -0
  280. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/app-update-service.js +0 -0
  281. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/application-state.js +0 -0
  282. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/async-result.js +0 -0
  283. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/auth.js +0 -0
  284. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/client-state-api.js +0 -0
  285. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/client-state-migration.js +0 -0
  286. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/activation-auth-policy.js +0 -0
  287. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/backup-command-availability.js +0 -0
  288. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
  289. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/command-palette-controller.js +0 -0
  290. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/endpoint-registry.js +0 -0
  291. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/note-layout-preferences.js +0 -0
  292. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/preferences-store.js +0 -0
  293. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/tag-config-loader.js +0 -0
  294. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/usage-store.js +0 -0
  295. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/config.js +0 -0
  296. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/connectivity-monitor.js +0 -0
  297. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/context-menu/context-menu-target-service.js +0 -0
  298. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
  299. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/disconnect-editing-state-service.js +0 -0
  300. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/dom-utils-fixed.js +0 -0
  301. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/dom-utils.js +0 -0
  302. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/editor-commands.js +0 -0
  303. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/editor-selection.js +0 -0
  304. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/editor-toolbar.js +0 -0
  305. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/elapsed-time.js +0 -0
  306. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/error-handler.js +0 -0
  307. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/error-overlay.js +0 -0
  308. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-actions.js +0 -0
  309. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-autosave.js +0 -0
  310. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-editor-service.js +0 -0
  311. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-editor-state.js +0 -0
  312. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-loader.js +0 -0
  313. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-preview-service.js +0 -0
  314. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-scene.js +0 -0
  315. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/expected-errors.js +0 -0
  316. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/location-flags.js +0 -0
  317. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/login-namespace-picker.js +0 -0
  318. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/maintenance.js +0 -0
  319. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/ai-agent-settings-modal.js +0 -0
  320. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
  321. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/backup-restore-modal.js +0 -0
  322. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/backup-result-modal.js +0 -0
  323. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
  324. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/backup-settings-modal.js +0 -0
  325. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/base-modal.js +0 -0
  326. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/confirmation-modal.js +0 -0
  327. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/delete-namespace-modal.js +0 -0
  328. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/delete-namespace-validation.js +0 -0
  329. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/dictation-settings-modal.js +0 -0
  330. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/help-modal.js +0 -0
  331. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/link-title-retry-modal.js +0 -0
  332. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/namespace-loading-page.js +0 -0
  333. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/namespace-modals.js +0 -0
  334. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/namespace-ports-profile.js +0 -0
  335. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/namespace-rename-validation.js +0 -0
  336. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/note-layout-appearance-modal.js +0 -0
  337. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/ontology-modal.js +0 -0
  338. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/password-modal.js +0 -0
  339. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/prioritize-modal.js +0 -0
  340. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/random-password-modal.js +0 -0
  341. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/reminder-modal.js +0 -0
  342. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
  343. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/search-suggestion-statistics-modal.js +0 -0
  344. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
  345. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/tag-suggestion-settings-modal.js +0 -0
  346. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/version-info-modal.js +0 -0
  347. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/visual-cues-modal.js +0 -0
  348. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/actions/content-actions.js +0 -0
  349. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
  350. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/actions/note-actions.js +0 -0
  351. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
  352. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
  353. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/actions/ui-actions.js +0 -0
  354. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/focus-events.js +0 -0
  355. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
  356. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
  357. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/keyboard-events.js +0 -0
  358. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/mouse-events.js +0 -0
  359. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
  360. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/mode-context.js +0 -0
  361. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/mode-logger.js +0 -0
  362. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
  363. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/add-style-service.js +0 -0
  364. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
  365. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/backlinks-navigation-service.js +0 -0
  366. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
  367. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
  368. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
  369. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
  370. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -0
  371. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/deselect-selection-state-service.js +0 -0
  372. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/dictation-note-paste.js +0 -0
  373. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/dictation-paste-padding.js +0 -0
  374. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/dictation-paste-service.js +0 -0
  375. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/differential-view-service.js +0 -0
  376. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/dropped-file-preview-service.js +0 -0
  377. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
  378. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
  379. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
  380. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/file-drop-policy-service.js +0 -0
  381. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
  382. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
  383. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
  384. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/floating-note-dom.js +0 -0
  385. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/floating-note-layout.js +0 -0
  386. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/floating-note-service.js +0 -0
  387. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/footnote-navigation-service.js +0 -0
  388. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/history-selection-policy-service.js +0 -0
  389. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/history-shortcut-policy-service.js +0 -0
  390. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +0 -0
  391. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +0 -0
  392. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +0 -0
  393. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
  394. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
  395. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/link-title-prefetch-service.js +0 -0
  396. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
  397. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/mermaid-render-service.js +0 -0
  398. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
  399. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
  400. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
  401. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
  402. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-delete-shortcut-policy-service.js +0 -0
  403. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-drag-indicator-service.js +0 -0
  404. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
  405. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-fullscreen-service.js +0 -0
  406. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
  407. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
  408. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-timestamp-hover-service.js +0 -0
  409. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
  410. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/paste-format-detection-service.js +0 -0
  411. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
  412. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/reference-source-navigation-service.js +0 -0
  413. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/remote-image-proxy-service.js +0 -0
  414. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/remove-formatting-service.js +0 -0
  415. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
  416. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/root-note-creation-service.js +0 -0
  417. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
  418. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
  419. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
  420. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
  421. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
  422. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
  423. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
  424. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
  425. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
  426. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
  427. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-suggestion-statistics-service.js +0 -0
  428. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-suggestion-window-labels-service.js +0 -0
  429. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-suggestion-windows-service.js +0 -0
  430. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
  431. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/selected-formatting-removal-service.js +0 -0
  432. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/selected-text-tag-service.js +0 -0
  433. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
  434. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
  435. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tab-drag-service.js +0 -0
  436. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
  437. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -0
  438. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tag-bar-service.js +0 -0
  439. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tag-input-selection-service.js +0 -0
  440. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tag-proposal-service.js +0 -0
  441. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +0 -0
  442. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
  443. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/untagged-view-indicator-service.js +0 -0
  444. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
  445. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/viewport-hold-service.js +0 -0
  446. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/note-html-sanitizer.js +0 -0
  447. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/password-generator.js +0 -0
  448. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/password-operation-response.js +0 -0
  449. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/password-policy.js +0 -0
  450. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/password-strength.js +0 -0
  451. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/reminder-store.js +0 -0
  452. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/reminder-surface-service.js +0 -0
  453. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/selection-markers.js +0 -0
  454. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/session-auth.js +0 -0
  455. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/tag-token.js +0 -0
  456. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/uuid.js +0 -0
  457. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/namespace-deleted.js +0 -0
  458. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/namespace-renamed.js +0 -0
  459. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/dompurify-3.4.16.LICENSE.txt +0 -0
  460. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/dompurify-3.4.16.min.js +0 -0
  461. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/excalidraw.css +0 -0
  462. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Bold.woff2 +0 -0
  463. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Medium.woff2 +0 -0
  464. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Regular.woff2 +0 -0
  465. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-SemiBold.woff2 +0 -0
  466. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Cascadia/CascadiaCode-Regular.woff2 +0 -0
  467. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2 +0 -0
  468. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.woff2 +0 -0
  469. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.woff2 +0 -0
  470. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.woff2 +0 -0
  471. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.woff2 +0 -0
  472. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.woff2 +0 -0
  473. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-41b173a47b57366892116a575a43e2b6.woff2 +0 -0
  474. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.woff2 +0 -0
  475. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2 +0 -0
  476. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.woff2 +0 -0
  477. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.woff2 +0 -0
  478. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 +0 -0
  479. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 +0 -0
  480. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2 +0 -0
  481. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2 +0 -0
  482. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2 +0 -0
  483. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2 +0 -0
  484. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2 +0 -0
  485. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Virgil/Virgil-Regular.woff2 +0 -0
  486. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1.LICENSE.txt +0 -0
  487. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1.min.js +0 -0
  488. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/markdown-it-14.3.2.LICENSE.txt +0 -0
  489. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/markdown-it-14.3.2.min.js +0 -0
  490. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/mermaid-11.16.1.LICENSE.txt +0 -0
  491. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/mermaid-11.16.1.min.js +0 -0
  492. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/zxcvbn-4.4.2.LICENSE.txt +0 -0
  493. {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/zxcvbn-4.4.2.js +0 -0
  494. {metalist-0.17.0 → metalist-0.18.0}/app/static/note-html-policy.json +0 -0
  495. {metalist-0.17.0 → metalist-0.18.0}/app/templates/_logo_mark.html +0 -0
  496. {metalist-0.17.0 → metalist-0.18.0}/app/templates/base.html +0 -0
  497. {metalist-0.17.0 → metalist-0.18.0}/app/templates/index.html +0 -0
  498. {metalist-0.17.0 → metalist-0.18.0}/app/templates/locked.html +0 -0
  499. {metalist-0.17.0 → metalist-0.18.0}/app/templates/maintenance.html +0 -0
  500. {metalist-0.17.0 → metalist-0.18.0}/app/templates/namespace_deleted.html +0 -0
  501. {metalist-0.17.0 → metalist-0.18.0}/app/templates/namespace_renamed.html +0 -0
  502. {metalist-0.17.0 → metalist-0.18.0}/app/templates/notes_list.html +0 -0
  503. {metalist-0.17.0 → metalist-0.18.0}/app/upload_limits.py +0 -0
  504. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/__init__.py +0 -0
  505. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/add_selected_text_tag.py +0 -0
  506. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/alphabetize_root_notes.py +0 -0
  507. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/base.py +0 -0
  508. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/bulk_tag_proposals.py +0 -0
  509. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/collapse.py +0 -0
  510. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/copy_note.py +0 -0
  511. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/create_child.py +0 -0
  512. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/create_file_preview_child.py +0 -0
  513. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/create_note.py +0 -0
  514. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/create_sibling.py +0 -0
  515. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/delete_subtree.py +0 -0
  516. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/delete_tag.py +0 -0
  517. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/expand.py +0 -0
  518. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/file_edit_session.py +0 -0
  519. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/indent.py +0 -0
  520. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/lock.py +0 -0
  521. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/move.py +0 -0
  522. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/move_to_top.py +0 -0
  523. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/outdent.py +0 -0
  524. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/paste_child.py +0 -0
  525. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/paste_sibling.py +0 -0
  526. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/prefetch_link_titles.py +0 -0
  527. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/prioritize.py +0 -0
  528. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/redo.py +0 -0
  529. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/rename_tag.py +0 -0
  530. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/reset_updated_at.py +0 -0
  531. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/resize_image.py +0 -0
  532. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/run_shell.py +0 -0
  533. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/search_comment_autofill.py +0 -0
  534. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/search_context_tags.py +0 -0
  535. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/set_collapse_bulk.py +0 -0
  536. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/set_collapse_in_context.py +0 -0
  537. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/split_note.py +0 -0
  538. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/tag_proposals.py +0 -0
  539. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/toggle_reference_mode.py +0 -0
  540. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/toggle_todo_done.py +0 -0
  541. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/undo.py +0 -0
  542. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/unformat_content.py +0 -0
  543. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/update_content.py +0 -0
  544. {metalist-0.17.0 → metalist-0.18.0}/app/usecases/view.py +0 -0
  545. {metalist-0.17.0 → metalist-0.18.0}/app/utils/__init__.py +0 -0
  546. {metalist-0.17.0 → metalist-0.18.0}/app/utils/encryption.py +0 -0
  547. {metalist-0.17.0 → metalist-0.18.0}/app/utils/text_utils.py +0 -0
  548. {metalist-0.17.0 → metalist-0.18.0}/convert-from-legacy.py +0 -0
  549. {metalist-0.17.0 → metalist-0.18.0}/main.py +0 -0
  550. {metalist-0.17.0 → metalist-0.18.0}/metalist.egg-info/dependency_links.txt +0 -0
  551. {metalist-0.17.0 → metalist-0.18.0}/metalist.egg-info/entry_points.txt +0 -0
  552. {metalist-0.17.0 → metalist-0.18.0}/metalist.egg-info/requires.txt +0 -0
  553. {metalist-0.17.0 → metalist-0.18.0}/metalist.egg-info/top_level.txt +0 -0
  554. {metalist-0.17.0 → metalist-0.18.0}/pyproject.toml +0 -0
  555. {metalist-0.17.0 → metalist-0.18.0}/scripts/generate-lan-cert.sh +0 -0
  556. {metalist-0.17.0 → metalist-0.18.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metalist
3
- Version: 0.17.0
3
+ Version: 0.18.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,11 @@ 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.18.0
55
+
56
+ - Right-click a tag suggestion, in the tag bar or the search bar, to see why it is suggested. The menu starts with **Why suggested**: the words of the note that name the tag (or a synonym), the tags it goes with and on how many notes, notes sharing this note's tags, whether it already applies through a parent or a rule, or that nothing in the note points to it; then how often the tag is used. Search suggestions also say how often you worked with a tag lately (when that put it first), what your search so far finds, or what leaving it out removes. The reasons are MetaList's own ranking, not the AI's, and appear instantly.
57
+ - Fixed: right-clicking a suggestion closed the list and opened the menu of the note underneath (or the browser's own menu). The list, your typed letters and the cursor now stay as they were, and `Escape` on an open right-click menu closes only the menu instead of also leaving edit mode.
58
+
54
59
  ## Changes in 0.17.0
55
60
 
56
61
  - A whole-view summary that fails or is stopped keeps the batches it finished; saying "try that again" continues it, running only the remaining batches with the original question.
@@ -12,6 +12,11 @@ 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.18.0
16
+
17
+ - Right-click a tag suggestion, in the tag bar or the search bar, to see why it is suggested. The menu starts with **Why suggested**: the words of the note that name the tag (or a synonym), the tags it goes with and on how many notes, notes sharing this note's tags, whether it already applies through a parent or a rule, or that nothing in the note points to it; then how often the tag is used. Search suggestions also say how often you worked with a tag lately (when that put it first), what your search so far finds, or what leaving it out removes. The reasons are MetaList's own ranking, not the AI's, and appear instantly.
18
+ - Fixed: right-clicking a suggestion closed the list and opened the menu of the note underneath (or the browser's own menu). The list, your typed letters and the cursor now stay as they were, and `Escape` on an open right-click menu closes only the menu instead of also leaving edit mode.
19
+
15
20
  ## Changes in 0.17.0
16
21
 
17
22
  - A whole-view summary that fails or is stopped keeps the batches it finished; saying "try that again" continues it, running only the remaining batches with the original question.
@@ -138,7 +138,8 @@ from app.services.note_fullscreen import build_note_fullscreen_markup
138
138
  from app.services.link_titles import link_title_store
139
139
  from app.services.search_index import search_index
140
140
  from app.services.dictation_cleanup import clean_dictated_paste
141
- from app.services.tag_suggestions import suggest_tags_for_note
141
+ from app.services.tag_suggestion_reasons import search_suggestion_reasons
142
+ from app.services.tag_suggestions import suggest_tags_with_reasons_for_note
142
143
  from app.services.client_state_service import suggests_already_applying_tags
143
144
  from app.services.undo_state import reset_undo_stack
144
145
  from app.usecases.prioritize import list_prioritize_tag_suggestions
@@ -496,9 +497,20 @@ def search_suggestions(request: Request, payload: SearchSuggestionsRequest) -> D
496
497
  for selection in personalized_selections
497
498
  if selection.tag in suggestions
498
499
  ]
500
+ # Why each is suggested, shown when it is right-clicked (docs/ui/controls.md).
501
+ facts = search_index.describe_search_suggestions(query=query, terms=suggestions)
502
+ recent_activity = {
503
+ selection.tag.casefold(): (selection.window_days, selection.activity_count)
504
+ for selection in personalized_selections
505
+ if selection.tag in suggestions
506
+ }
499
507
  return {
500
508
  "suggestions": suggestions,
501
509
  "personalizedSuggestions": personalized_suggestions,
510
+ "reasons": {
511
+ tag: list(search_suggestion_reasons(facts=facts, term=tag, recent_activity=recent_activity))
512
+ for tag in suggestions
513
+ },
502
514
  }
503
515
 
504
516
 
@@ -626,7 +638,7 @@ def tag_suggestions(request: Request, payload: TagSuggestionsRequest) -> Dict[st
626
638
 
627
639
  _require_note_present(note_id, context="notes.tag-suggestions")
628
640
 
629
- suggestions = suggest_tags_for_note(
641
+ suggestions = suggest_tags_with_reasons_for_note(
630
642
  note_id=note_id,
631
643
  anchors=anchors,
632
644
  explicit_tags=explicit_tags,
@@ -635,7 +647,11 @@ def tag_suggestions(request: Request, payload: TagSuggestionsRequest) -> Dict[st
635
647
  limit=MAX_TAG_SUGGESTIONS,
636
648
  include_already_applying=suggests_already_applying_tags(token=_require_bearer_token(request)),
637
649
  )
638
- return {"suggestions": suggestions}
650
+ # Why each is suggested, shown when it is right-clicked (docs/ui/tag-bar.md).
651
+ return {
652
+ "suggestions": [suggestion.tag for suggestion in suggestions],
653
+ "reasons": {suggestion.tag: list(suggestion.reasons) for suggestion in suggestions},
654
+ }
639
655
 
640
656
 
641
657
  @router.get("/notes/export-html")
@@ -172,7 +172,7 @@ On macOS, `⌘ + Y` normally opens Safari History rather than performing native
172
172
  | Right-click image in note | Adds image actions to the note context menu: `Copy Image`, `Save Image`, `Zoom Image`, `Open Image in New Tab` |
173
173
  | Right-click anywhere in a completed AI response bubble (including padding and links) | Choose `Copy Response` to place its raw Markdown on the MetaList note clipboard with `@markdown @llm`, while also writing rendered HTML and raw Markdown to the system clipboard. It can be pasted as a note but not as a reference until inserted. |
174
174
  | Right-click notes view background | Open the view menu: outside edit mode `Add Note at Top`, `Paste Note at Top` (once a note is copied), `Undo`, `Redo`; `Go to Top`, `Go to Bottom` (unless at that end), `Fully Expand All`, `Fully Collapse All`; `View ▸` (All / Untagged notes), `Sort by ▸`, `Show ▸` (Chat, Tabs, Tags in List), `Theme ▸` (System, Light, Dark), each ticking the current choice; `New Tab`, `Open/Switch Namespace…`, `Reminders…`; `Export View as HTML`; `Log Out`. Shortcuts show on the right; items that do not apply are hidden. |
175
- | Right-click tag in search input, tag bar, or their suggestion lists | Open tag context menu (Edit Tag Relationships). If a note is being edited, it is saved and the view refresh completes first, restoring view-only rendering such as cached URL titles. |
175
+ | Right-click tag in search input, tag bar, or their suggestion lists | Open tag context menu (Edit Tag Relationships); a suggestion's menu (tag bar or search) starts with **Why suggested**. A suggestion list, the typed letters and edit mode stay as they were. Choosing Edit Tag Relationships saves an edited note first and completes the view refresh, restoring view-only rendering such as cached URL titles. |
176
176
  | Right-click left or right lane | Open the same view menu |
177
177
  | Hover left side of search bar | Show the tab/search-context overlay |
178
178
  | Hover/click search input | Hide the tab/search-context overlay |
@@ -201,7 +201,7 @@ On macOS, `⌘ + Y` normally opens Safari History rather than performing native
201
201
 
202
202
  > Note: On Windows/Linux, use `Ctrl` instead of `⌘` (Command)
203
203
 
204
- App menus close with `Escape` or an outside click. The right-click menu also closes once the pointer moves about 28px away from it and any open submenu, like the tab overlay; small movements just past its edge keep it open. Every modal has a standard upper-right circular `×` matching full-screen note view and also closes with `Escape` or an outside click. Dismiss-only footer `Close`/`OK` buttons are omitted; `Enter` still submits a modal's declared primary workflow action.
204
+ App menus close with `Escape` (only the menu: a note being edited stays in edit mode) or an outside click. The right-click menu also closes once the pointer moves about 28px away from it and any open submenu, like the tab overlay; small movements just past its edge keep it open. Every modal has a standard upper-right circular `×` matching full-screen note view and also closes with `Escape` or an outside click. Dismiss-only footer `Close`/`OK` buttons are omitted; `Enter` still submits a modal's declared primary workflow action.
205
205
 
206
206
  When `Show tags in list` is enabled, the grey right-aligned tag column wraps and is capped at 25% of its note row so note content retains the majority of the available width.
207
207
 
@@ -5,6 +5,11 @@ README. Changes made after the newest listed version are not described here.
5
5
  Compare with installed_version from the help lookup to tell the user whether
6
6
  they already have a release. Version info offers updates (see the data topic).
7
7
 
8
+ ## 0.18.0
9
+
10
+ - Right-click a tag suggestion, in the tag bar or the search bar, to see why it is suggested. The menu starts with **Why suggested**: the words of the note that name the tag (or a synonym), the tags it goes with and on how many notes, notes sharing this note's tags, whether it already applies through a parent or a rule, or that nothing in the note points to it; then how often the tag is used. Search suggestions also say how often you worked with a tag lately (when that put it first), what your search so far finds, or what leaving it out removes. The reasons are MetaList's own ranking, not the AI's, and appear instantly.
11
+ - Fixed: right-clicking a suggestion closed the list and opened the menu of the note underneath (or the browser's own menu). The list, your typed letters and the cursor now stay as they were, and `Escape` on an open right-click menu closes only the menu instead of also leaving edit mode.
12
+
8
13
  ## 0.17.0
9
14
 
10
15
  - A whole-view summary that fails or is stopped keeps the batches it finished; saying "try that again" continues it, running only the remaining batches with the original question.
@@ -144,6 +144,8 @@ Ontology rules also add **inferred tags** before search matching:
144
144
  - A typed prefix can match either the start of the full tag or the start of any connector-separated segment.
145
145
  - Example: `wor` suggests `workspaces` and `databricks-workspaces`.
146
146
  - Example: `orksp` suggests neither.
147
+ - Right-clicking a search suggestion (Control-click on a Mac) opens its tag menu: **Why suggested**, then Edit Tag Relationships. The search and the list stay as they were; `Escape` closes only the menu. A right-click earns no activity credit.
148
+ - **Why suggested** says what MetaList's ranking used (not the AI): how often you worked with the tag in a personalization window (`You've worked with it 12 times in the last 7 days`), the notes it shares with the tags already in the search, how many notes the search so far finds it on, how many notes leaving it out removes, or that a first tag is listed by use; last, how many notes searching for it finds.
147
149
  - The server returns at most `MAX_SEARCH_SUGGESTIONS` suggestions; the default is 20.
148
150
  - This affects suggestions only; actual tag search matching remains exact against effective tag terms.
149
151
 
@@ -161,6 +161,8 @@ Unclosed comments behave like unclosed wrappers:
161
161
  - Tags declared in ontology rules remain eligible for literal-content matching and typed-prefix completion even when they have never been explicitly assigned to a note. With a blank prefix, unrelated unused ontology tags are not added as generic fallback suggestions.
162
162
  - Tags already present explicitly in the current tag bar are never suggested.
163
163
  - Tags already present only via inheritance or ontology inference are suppressed unless they match the prefix. For one-character prefixes they come right after the suggestions with evidence; for longer prefixes they appear at the bottom. The **Tag Suggestions** settings dialog has `Allow suggesting tags that already apply` (off by default): when on, tags a parent already has and tags the rules imply from them are ranked like any other tag. Tags written in the note's own bar are still never suggested.
164
+ - Right-clicking a suggestion (Control-click on a Mac) opens the tag menu for it: **Why suggested**, then Edit Tag Relationships. The list, the typed letters and edit mode stay as they were; `Escape` closes only the menu.
165
+ - **Why suggested** says what the ranking found, then how often the tag is used: named (or partly named) in the note's text, a synonym named in it, the rest of the tag after what was typed, on N notes together with the note's tags, named in the note as last saved, on N notes that share the note's tags, or already applying through a parent or a rule. `Nothing in this note points to it` means it is listed only because it is used often. The reasons are MetaList's own ranking, not the AI's.
164
166
  - Suggestions appear below the tag bar when there is room; if the tag bar is near the bottom, the list flips upward but keeps the same best-on-top ordering and initial scroll position.
165
167
  - The server returns at most `MAX_TAG_SUGGESTIONS` note tag suggestions; the default is 20.
166
168
 
@@ -52,6 +52,8 @@ class TagActivityState:
52
52
  class TagActivityWindowSelection:
53
53
  tag: str
54
54
  window_days: int
55
+ # The tag's activity credits in that window (why it won the slot).
56
+ activity_count: int
55
57
 
56
58
 
57
59
  def current_local_date() -> date:
@@ -153,6 +155,7 @@ def rank_tag_activity_window_selections(
153
155
  TagActivityWindowSelection(
154
156
  tag=candidate_by_casefold[winner_casefold],
155
157
  window_days=day_count,
158
+ activity_count=totals_by_casefold[winner_casefold],
156
159
  )
157
160
  )
158
161
  return selections
@@ -128,6 +128,42 @@ def _parse_search_query_for_suggestions(raw_input: str) -> tuple[Tuple[str, ...]
128
128
  return context.anchors, context.partial_prefix
129
129
 
130
130
 
131
+ @dataclass(frozen=True, slots=True)
132
+ class TagCooccurrence:
133
+ """A tag's notes that carry the most of some anchor tags, as tag suggestions rank them."""
134
+
135
+ anchor_count: int
136
+ note_count: int
137
+ # The anchors (casefolded) those notes carry; one set when they all carry the same ones.
138
+ anchor_sets: FrozenSet[FrozenSet[str]]
139
+
140
+ def __post_init__(self) -> None:
141
+ if self.anchor_count <= 0 or self.note_count <= 0:
142
+ raise ValueError("A tag co-occurrence needs at least one anchor and one note")
143
+ if not self.anchor_sets or any(len(anchors) != self.anchor_count for anchors in self.anchor_sets):
144
+ raise ValueError("Every anchor set holds anchor_count anchors")
145
+
146
+
147
+ @dataclass(frozen=True, slots=True)
148
+ class SearchSuggestionFacts:
149
+ """What the search-bar ranking used for some shown suggestions, to say why each is there."""
150
+
151
+ partial_prefix: str
152
+ # Tags (and words) already typed in the clause; with them the ranking is by co-occurrence.
153
+ anchors: Tuple[str, ...]
154
+ has_completed_clause: bool
155
+ is_exclusion: bool
156
+ # Notes the search so far finds (every note when nothing precedes the typed tag).
157
+ matching_note_count: int
158
+ note_counts: Dict[str, int]
159
+ matching_counts: Dict[str, int]
160
+ cooccurrences: Dict[str, TagCooccurrence]
161
+
162
+ @property
163
+ def is_ranked_by_cooccurrence(self) -> bool:
164
+ return any(anchor and not anchor.startswith("@") for anchor in self.anchors)
165
+
166
+
131
167
  @dataclass(frozen=True, slots=True)
132
168
  class SearchRecord:
133
169
  note_id: str
@@ -513,6 +549,79 @@ class SearchIndex:
513
549
  if term and not term.startswith("@")
514
550
  }
515
551
 
552
+ def describe_tag_cooccurrence(self, *, anchors: Iterable[str], terms: Iterable[str]) -> Dict[str, TagCooccurrence]:
553
+ """For each term, the notes carrying it together with the most of the anchors.
554
+
555
+ The same measure `suggest_tag_completions` ranks by (the most anchors one
556
+ of the term's notes carries, and how many notes carry that many), with
557
+ the anchors named. Terms on no note with an anchor are left out.
558
+ """
559
+ anchor_casefold_set = frozenset(
560
+ anchor.casefold() for anchor in anchors if anchor and not anchor.startswith("@")
561
+ )
562
+ described: Dict[str, TagCooccurrence] = {}
563
+ with self._lock:
564
+ for term in terms:
565
+ term_casefold = term.casefold()
566
+ if term_casefold not in self._tag_notes_casefold:
567
+ continue
568
+ term_notes = self._tag_notes_casefold[term_casefold]
569
+ anchors_by_note: Dict[int, Set[str]] = {}
570
+ for anchor_casefold in anchor_casefold_set:
571
+ if anchor_casefold == term_casefold or anchor_casefold not in self._tag_notes_casefold:
572
+ continue
573
+ for note_id in term_notes & self._tag_notes_casefold[anchor_casefold]:
574
+ anchors_by_note.setdefault(note_id, set()).add(anchor_casefold)
575
+ if not anchors_by_note:
576
+ continue
577
+ most_anchors = max(len(note_anchors) for note_anchors in anchors_by_note.values())
578
+ at_most = [frozenset(note_anchors) for note_anchors in anchors_by_note.values()
579
+ if len(note_anchors) == most_anchors]
580
+ described[term] = TagCooccurrence(
581
+ anchor_count=most_anchors, note_count=len(at_most), anchor_sets=frozenset(at_most),
582
+ )
583
+ return described
584
+
585
+ def describe_search_suggestions(self, *, query: str, terms: Iterable[str]) -> SearchSuggestionFacts:
586
+ """The facts `suggest_all_tag_completions` ranked and filtered these terms by."""
587
+ context = _parse_search_suggestion_context(query)
588
+ partial_prefix = ""
589
+ if context.partial_prefix is not None:
590
+ partial_prefix = context.partial_prefix
591
+ shown = list(terms)
592
+ with self._lock:
593
+ has_completed_clause = context.completed_clause.strip() != ""
594
+ if has_completed_clause:
595
+ clause = parse_search_query(context.completed_clause).first_clause
596
+ matching_ids = self._matching_note_int_ids_for_clause_locked(clause)
597
+ else:
598
+ matching_ids = self._alive
599
+ note_counts: Dict[str, int] = {}
600
+ matching_counts: Dict[str, int] = {}
601
+ for term in shown:
602
+ posting: FrozenSet[int] = frozenset()
603
+ if term.casefold() in self._tag_notes_casefold:
604
+ posting = frozenset(self._tag_notes_casefold[term.casefold()])
605
+ note_counts[term] = len(posting)
606
+ matching_counts[term] = len(posting & matching_ids)
607
+ cooccurrences = self.describe_tag_cooccurrence(anchors=context.anchors, terms=shown)
608
+ return SearchSuggestionFacts(
609
+ partial_prefix=partial_prefix, anchors=tuple(context.anchors),
610
+ has_completed_clause=has_completed_clause, is_exclusion=context.is_exclusion,
611
+ matching_note_count=len(matching_ids), note_counts=note_counts,
612
+ matching_counts=matching_counts, cooccurrences=cooccurrences,
613
+ )
614
+
615
+ def count_notes_with_tag(self, term: str) -> int:
616
+ """Notes carrying the tag, as suggestions count usage (case-insensitive)."""
617
+ if not isinstance(term, str) or term == "":
618
+ raise TypeError("term must be a non-empty string")
619
+ with self._lock:
620
+ term_casefold = term.casefold()
621
+ if term_casefold not in self._tag_notes_casefold:
622
+ return 0
623
+ return len(self._tag_notes_casefold[term_casefold])
624
+
516
625
  def suggest_tag_completions(self, *, query: str, limit: int) -> List[str]:
517
626
  if not isinstance(query, str):
518
627
  raise TypeError(f"query must be a string, got {type(query)}")
@@ -0,0 +1,326 @@
1
+ """Why a tag is suggested, in the tag bar or the search bar: short reasons shown on right-click.
2
+
3
+ The facts come from the rankings (`tag_suggestions` for the tag bar: what the
4
+ note's text names, which of its tags the tag goes with, which notes share its
5
+ tags; the search index and search history for the search bar): the reasons say
6
+ what the ranking used, never a second guess at it. Only the shown suggestions
7
+ are explained (docs/ui/tag-bar.md, docs/ui/controls.md).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from typing import Dict, FrozenSet, Iterable, List, Mapping, Tuple
14
+
15
+ from app.services.search_index import SearchIndex
16
+ from app.services.search_index import SearchSuggestionFacts
17
+ from app.services.search_index import TagCooccurrence
18
+ from app.services.tag_ontology import TagOntology
19
+ from app.services.tag_term_matching import TagContentMatch
20
+ from app.services.tag_term_matching import quote_content_match
21
+
22
+ NOTHING_POINTS_TO_IT = "Nothing in this note points to it"
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class TagSuggestion:
27
+ tag: str
28
+ reasons: Tuple[str, ...]
29
+
30
+ def __post_init__(self) -> None:
31
+ if not isinstance(self.tag, str) or self.tag == "":
32
+ raise ValueError("A tag suggestion needs a tag")
33
+ if not isinstance(self.reasons, tuple) or not self.reasons:
34
+ raise ValueError(f"Tag suggestion {self.tag!r} needs at least one reason")
35
+ for reason in self.reasons:
36
+ if not isinstance(reason, str) or reason.strip() == "":
37
+ raise ValueError(f"Tag suggestion {self.tag!r} has an empty reason")
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class ContextOverlap:
42
+ """Other notes that share the most of this note's tags and have the term written on them."""
43
+
44
+ shared_count: int
45
+ note_count: int
46
+ # The shared tags (casefolded) of those notes; a second set means they differ.
47
+ shared_casefold_sets: Tuple[FrozenSet[str], ...]
48
+
49
+ def __post_init__(self) -> None:
50
+ assert self.shared_count > 0 and self.note_count > 0
51
+ assert 1 <= len(self.shared_casefold_sets) <= 2
52
+ assert all(len(shared) == self.shared_count for shared in self.shared_casefold_sets)
53
+
54
+
55
+ def _quoted(text: str) -> str:
56
+ return f'"{text}"'
57
+
58
+
59
+ def _join_names(names: Iterable[str]) -> str:
60
+ quoted = [_quoted(name) for name in sorted(names, key=lambda name: (name.casefold(), name))]
61
+ assert quoted
62
+ if len(quoted) == 1:
63
+ return quoted[0]
64
+ return ", ".join(quoted[:-1]) + " and " + quoted[-1]
65
+
66
+
67
+ def _count_notes(count: int) -> str:
68
+ assert count > 0
69
+ if count == 1:
70
+ return "1 note"
71
+ return f"{count:,} notes"
72
+
73
+
74
+ def _usage_line(count: int) -> str:
75
+ assert count > 0
76
+ return f"You've tagged {_count_notes(count)} with it"
77
+
78
+
79
+ def _names_for(casefold_names: FrozenSet[str], display_by_casefold: Mapping[str, str]) -> List[str]:
80
+ for name in casefold_names:
81
+ assert name in display_by_casefold, name
82
+ return [display_by_casefold[name] for name in casefold_names]
83
+
84
+
85
+ def _cooccurrence_phrase(cooccurrence: TagCooccurrence, anchors: Iterable[str], anchors_name: str) -> str:
86
+ """`on 4 notes together with "python"`, naming the anchors those notes carry
87
+ (or how many of them, `anchors_name` saying whose they are)."""
88
+ notes = _count_notes(cooccurrence.note_count)
89
+ if len(cooccurrence.anchor_sets) > 1:
90
+ return f"on {notes} together with {cooccurrence.anchor_count} of {anchors_name}"
91
+ display_by_casefold = {anchor.casefold(): anchor for anchor in anchors}
92
+ (anchor_set,) = cooccurrence.anchor_sets
93
+ return f"on {notes} together with {_join_names(_names_for(anchor_set, display_by_casefold))}"
94
+
95
+
96
+ def _count_times(count: int) -> str:
97
+ assert count > 0
98
+ if count == 1:
99
+ return "once"
100
+ if count == 2:
101
+ return "twice"
102
+ return f"{count:,} times"
103
+
104
+
105
+ @dataclass(frozen=True)
106
+ class ReasonWriter:
107
+ """Reasons any suggestion can have: whether it already applies, and its use."""
108
+
109
+ count_by_casefold: Mapping[str, int]
110
+ ontology_only_casefold: FrozenSet[str]
111
+ inherited_casefold: FrozenSet[str]
112
+ implied_casefold: FrozenSet[str]
113
+ base_tags: FrozenSet[str]
114
+ ontology: TagOntology
115
+
116
+ @classmethod
117
+ def build(
118
+ cls, *, exact_tag_counts: Mapping[str, int], ontology_only_casefold: FrozenSet[str],
119
+ inherited_non_meta: FrozenSet[str], base_tags: FrozenSet[str], ontology: TagOntology,
120
+ ) -> ReasonWriter:
121
+ count_by_casefold: Dict[str, int] = {}
122
+ for term, count in exact_tag_counts.items():
123
+ assert term.casefold() not in count_by_casefold, f"case variants are merged before counting: {term}"
124
+ count_by_casefold[term.casefold()] = count
125
+ base_casefold = frozenset(tag.casefold() for tag in base_tags)
126
+ implied_casefold: FrozenSet[str] = frozenset()
127
+ if not ontology.is_empty:
128
+ implied_casefold = frozenset(
129
+ tag.casefold() for tag in ontology.infer_implication_only(base_tags=base_tags)
130
+ ) - base_casefold
131
+ return cls(
132
+ count_by_casefold=count_by_casefold, ontology_only_casefold=ontology_only_casefold,
133
+ inherited_casefold=frozenset(tag.casefold() for tag in inherited_non_meta),
134
+ implied_casefold=implied_casefold, base_tags=base_tags, ontology=ontology,
135
+ )
136
+
137
+ def already_applies(self, term: str) -> Tuple[str, ...]:
138
+ term_casefold = term.casefold()
139
+ if term_casefold in self.inherited_casefold:
140
+ return ("Already applies: inherited from a parent note",)
141
+ if term_casefold not in self.implied_casefold:
142
+ return ()
143
+ for base in sorted(self.base_tags, key=lambda tag: (tag.casefold(), tag)):
144
+ implied = self.ontology.infer_implication_only(base_tags=frozenset({base}))
145
+ if term_casefold in {tag.casefold() for tag in implied}:
146
+ return (f"Already applies: implied by {_quoted(base)}",)
147
+ # Implied only by several of the note's tags together.
148
+ return ("Already applies: implied by this note's tags",)
149
+
150
+ def usage(self, term: str) -> str:
151
+ term_casefold = term.casefold()
152
+ if term_casefold in self.count_by_casefold and self.count_by_casefold[term_casefold] > 0:
153
+ return _usage_line(self.count_by_casefold[term_casefold])
154
+ assert term_casefold in self.ontology_only_casefold, f"suggested tag {term!r} is on no note"
155
+ return "Not on any note yet; it is in your tag relationships"
156
+
157
+ def without_evidence(self, term: str) -> Tuple[str, ...]:
158
+ """A tag listed for its use (or because it already applies), not for this note."""
159
+ already = self.already_applies(term)
160
+ if already:
161
+ return (*already, self.usage(term))
162
+ return (NOTHING_POINTS_TO_IT, self.usage(term))
163
+
164
+
165
+ def meta_tag_reasons(*, index: SearchIndex, term: str, prefix: str) -> Tuple[str, ...]:
166
+ """Meta tags are listed by use once `@` is typed."""
167
+ assert term.startswith("@") and prefix.startswith("@"), (term, prefix)
168
+ count = index.count_notes_with_tag(term)
169
+ usage = "Not on any note yet"
170
+ if count > 0:
171
+ usage = _usage_line(count)
172
+ return (f"A built-in tag starting with {_quoted(prefix)}", usage)
173
+
174
+
175
+ @dataclass(frozen=True)
176
+ class SuggestionEvidence:
177
+ """What one ranking found for its terms, to say why a shown tag is suggested."""
178
+
179
+ # The index the ranking used, for the co-occurring notes behind a reason.
180
+ index: SearchIndex
181
+ writer: ReasonWriter
182
+ prefix: str
183
+ content_matches: Mapping[str, TagContentMatch]
184
+ # Terms whose content match is the rest of the tag after the typed prefix.
185
+ prefix_remainder_terms: FrozenSet[str]
186
+ # Synonym named exactly in the content -> (the tag it is a synonym of, its match).
187
+ synonym_matches: Mapping[str, Tuple[str, TagContentMatch]]
188
+ cooccurrence_terms: FrozenSet[str]
189
+ cooccurrence_anchors: Tuple[str, ...]
190
+ saved_text_matches: Mapping[str, TagContentMatch]
191
+ overlaps: Mapping[str, ContextOverlap]
192
+ context_tags: FrozenSet[str]
193
+ # Shown tag -> the ranked terms it stands for (equivalent tags are shown once).
194
+ sources: Mapping[str, Tuple[str, ...]]
195
+
196
+ def reasons_for(self, tag: str) -> Tuple[str, ...]:
197
+ assert tag in self.sources, f"{tag!r} was not ranked"
198
+ subjects = [tag] + [term for term in self.sources[tag] if term != tag]
199
+ cooccurrences = self.index.describe_tag_cooccurrence(
200
+ anchors=self.cooccurrence_anchors,
201
+ terms=[subject for subject in subjects if subject in self.cooccurrence_terms],
202
+ )
203
+ lines: List[str] = []
204
+ for subject in subjects:
205
+ lines.extend(self._evidence(subject=subject, shown=tag, cooccurrences=cooccurrences))
206
+ lines.extend(self.writer.already_applies(tag))
207
+ if not lines:
208
+ lines.append(NOTHING_POINTS_TO_IT)
209
+ lines.append(self.writer.usage(tag))
210
+ return tuple(dict.fromkeys(lines))
211
+
212
+ def _evidence(self, *, subject: str, shown: str, cooccurrences: Mapping[str, TagCooccurrence]) -> List[str]:
213
+ lines: List[str] = []
214
+ if subject in self.synonym_matches:
215
+ representative, match = self.synonym_matches[subject]
216
+ lines.append(self._named(match=match, shown=shown, where="this note", is_remainder=False))
217
+ lines.append(f"A synonym of {_quoted(representative)}")
218
+ elif subject in self.content_matches:
219
+ lines.append(self._named(
220
+ match=self.content_matches[subject], shown=shown, where="this note",
221
+ is_remainder=subject in self.prefix_remainder_terms,
222
+ ))
223
+ if subject in self.cooccurrence_terms:
224
+ assert subject in cooccurrences, f"{subject!r} was ranked as co-occurring"
225
+ lines.append(self._about(subject, shown, self._cooccurrence(cooccurrences[subject])))
226
+ if subject in self.saved_text_matches and subject not in self.content_matches:
227
+ lines.append(self._named(
228
+ match=self.saved_text_matches[subject], shown=shown, where="this note as last saved",
229
+ is_remainder=False,
230
+ ))
231
+ if subject in self.overlaps and subject not in self.cooccurrence_terms:
232
+ overlap = self._overlap(subject=subject, overlap=self.overlaps[subject])
233
+ if overlap != "":
234
+ lines.append(self._about(subject, shown, overlap))
235
+ return lines
236
+
237
+ def _named(self, *, match: TagContentMatch, shown: str, where: str, is_remainder: bool) -> str:
238
+ quoted = ", ".join(_quoted(words) for words in quote_content_match(match))
239
+ # Whole only when every part of the tag was found (`topic-41` is partly named by "topic").
240
+ is_whole = len(match.evidence_raw_indexes) == match.raw_segment_count
241
+ if is_remainder:
242
+ return f"The rest of it after {_quoted(self.prefix)} is in {where}: {quoted}"
243
+ if match.term.casefold() == shown.casefold():
244
+ if is_whole:
245
+ return f"Named in {where}: {quoted}"
246
+ return f"Partly named in {where}: {quoted}"
247
+ where_capitalized = where[0].upper() + where[1:]
248
+ if is_whole:
249
+ return f"{where_capitalized} names its synonym {_quoted(match.term)}"
250
+ return f"{where_capitalized} partly names its synonym {_quoted(match.term)}: {quoted}"
251
+
252
+ def _cooccurrence(self, cooccurrence: TagCooccurrence) -> str:
253
+ return _cooccurrence_phrase(cooccurrence, self.cooccurrence_anchors, "this note's tags")
254
+
255
+ def _overlap(self, *, subject: str, overlap: ContextOverlap) -> str:
256
+ """Empty when the only tag shared is the term itself (a tag that already
257
+ applies, ranked when that setting is on): notes with it have it."""
258
+ notes = _count_notes(overlap.note_count)
259
+ # The term itself is no evidence for itself.
260
+ shared_sets = [shared - {subject.casefold()} for shared in overlap.shared_casefold_sets]
261
+ if any(not shared for shared in shared_sets):
262
+ return ""
263
+ if len(shared_sets) > 1:
264
+ shared_count = min(len(shared) for shared in shared_sets)
265
+ return f"on {notes} sharing {shared_count} of this note's tags"
266
+ display_by_casefold = {tag.casefold(): tag for tag in self.context_tags}
267
+ names = _join_names(_names_for(shared_sets[0], display_by_casefold))
268
+ verb = "share"
269
+ if overlap.note_count == 1:
270
+ verb = "shares"
271
+ return f"on {notes} that {verb} {names} with this one"
272
+
273
+ @staticmethod
274
+ def _about(subject: str, shown: str, finding: str) -> str:
275
+ """A finding about the shown tag, or about a synonym it is shown in place of."""
276
+ if subject == shown:
277
+ return finding[0].upper() + finding[1:]
278
+ return f"Its synonym {_quoted(subject)} is {finding}"
279
+
280
+
281
+ def search_suggestion_reasons(
282
+ *, facts: SearchSuggestionFacts, term: str, recent_activity: Mapping[str, Tuple[int, int]],
283
+ ) -> Tuple[str, ...]:
284
+ """Why the search bar suggests a tag.
285
+
286
+ recent_activity: casefolded tag -> (window days, activity credits) for the
287
+ tags a personalization window put first.
288
+ """
289
+ assert term in facts.note_counts, f"{term!r} was not described"
290
+ lines: List[str] = []
291
+ if term.casefold() in recent_activity:
292
+ window_days, activity_count = recent_activity[term.casefold()]
293
+ when = "today"
294
+ if window_days > 1:
295
+ when = f"in the last {window_days} days"
296
+ lines.append(f"You've worked with it {_count_times(activity_count)} {when}")
297
+ note_count = facts.note_counts[term]
298
+ if facts.partial_prefix.startswith("@"):
299
+ lines.append(f"A built-in tag starting with {_quoted(facts.partial_prefix)}")
300
+ if note_count == 0:
301
+ lines.append("Not on any note yet")
302
+ return tuple(lines)
303
+ lines.append(f"Searching for it finds {_count_notes(note_count)}")
304
+ return tuple(lines)
305
+ if facts.is_exclusion:
306
+ lines.append(_exclusion_line(facts=facts, term=term))
307
+ elif term in facts.cooccurrences:
308
+ finding = _cooccurrence_phrase(facts.cooccurrences[term], facts.anchors, "the tags in your search")
309
+ lines.append(finding[0].upper() + finding[1:])
310
+ elif facts.has_completed_clause:
311
+ lines.append(f"Your search so far finds it on {_count_notes(facts.matching_counts[term])}")
312
+ else:
313
+ lines.append("Tags are listed by how many notes have them")
314
+ assert note_count > 0, f"suggested search tag {term!r} is on no note"
315
+ lines.append(f"Searching for it finds {_count_notes(note_count)}")
316
+ return tuple(lines)
317
+
318
+
319
+ def _exclusion_line(*, facts: SearchSuggestionFacts, term: str) -> str:
320
+ removed = facts.matching_counts[term]
321
+ of_notes = f"of your {facts.matching_note_count:,} notes"
322
+ if facts.has_completed_clause:
323
+ of_notes = f"of the {facts.matching_note_count:,} notes your search finds"
324
+ if removed == 0:
325
+ return f"Leaving it out removes none {of_notes}"
326
+ return f"Leaving it out removes {removed:,} {of_notes}"