MetaList 0.6.3__tar.gz → 0.7.1__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 (455) hide show
  1. {metalist-0.6.3 → metalist-0.7.1}/PKG-INFO +13 -1
  2. {metalist-0.6.3 → metalist-0.7.1}/README.md +12 -0
  3. {metalist-0.6.3 → metalist-0.7.1}/app/exception_boundaries.py +2 -2
  4. metalist-0.7.1/app/network_settings.py +61 -0
  5. {metalist-0.6.3 → metalist-0.7.1}/app/security/request_boundary.py +1 -74
  6. metalist-0.7.1/app/security/request_hosts.py +83 -0
  7. {metalist-0.6.3 → metalist-0.7.1}/app/services/note_store.py +117 -45
  8. {metalist-0.6.3 → metalist-0.7.1}/app/static/css/main.css +5 -0
  9. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/version-info-modal.js +5 -1
  10. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/tab-state-service.js +24 -3
  11. {metalist-0.6.3 → metalist-0.7.1}/app/version.py +1 -1
  12. {metalist-0.6.3 → metalist-0.7.1}/main.py +9 -0
  13. {metalist-0.6.3 → metalist-0.7.1}/metalist.egg-info/PKG-INFO +13 -1
  14. {metalist-0.6.3 → metalist-0.7.1}/metalist.egg-info/SOURCES.txt +2 -0
  15. {metalist-0.6.3 → metalist-0.7.1}/MANIFEST.in +0 -0
  16. {metalist-0.6.3 → metalist-0.7.1}/app/__init__.py +0 -0
  17. {metalist-0.6.3 → metalist-0.7.1}/app/api/__init__.py +0 -0
  18. {metalist-0.6.3 → metalist-0.7.1}/app/api/deps.py +0 -0
  19. {metalist-0.6.3 → metalist-0.7.1}/app/api/dev.py +0 -0
  20. {metalist-0.6.3 → metalist-0.7.1}/app/api/middleware/auth.py +0 -0
  21. {metalist-0.6.3 → metalist-0.7.1}/app/api/note_requests.py +0 -0
  22. {metalist-0.6.3 → metalist-0.7.1}/app/api/request_auth.py +0 -0
  23. {metalist-0.6.3 → metalist-0.7.1}/app/api/routes/ai.py +0 -0
  24. {metalist-0.6.3 → metalist-0.7.1}/app/api/routes/app_updates.py +0 -0
  25. {metalist-0.6.3 → metalist-0.7.1}/app/api/routes/auth.py +0 -0
  26. {metalist-0.6.3 → metalist-0.7.1}/app/api/routes/backups.py +0 -0
  27. {metalist-0.6.3 → metalist-0.7.1}/app/api/routes/files.py +0 -0
  28. {metalist-0.6.3 → metalist-0.7.1}/app/api/routes/notes.py +0 -0
  29. {metalist-0.6.3 → metalist-0.7.1}/app/api/routes/ontology.py +0 -0
  30. {metalist-0.6.3 → metalist-0.7.1}/app/api/routes/reminders.py +0 -0
  31. {metalist-0.6.3 → metalist-0.7.1}/app/api/routes/remote_images.py +0 -0
  32. {metalist-0.6.3 → metalist-0.7.1}/app/api/routes/test.py +0 -0
  33. {metalist-0.6.3 → metalist-0.7.1}/app/api/transactions.py +0 -0
  34. {metalist-0.6.3 → metalist-0.7.1}/app/api/upload_limits.py +0 -0
  35. {metalist-0.6.3 → metalist-0.7.1}/app/config.py +0 -0
  36. {metalist-0.6.3 → metalist-0.7.1}/app/data_directory.py +0 -0
  37. {metalist-0.6.3 → metalist-0.7.1}/app/db/__init__.py +0 -0
  38. {metalist-0.6.3 → metalist-0.7.1}/app/db/backup_settings_sql.py +0 -0
  39. {metalist-0.6.3 → metalist-0.7.1}/app/db/engine.py +0 -0
  40. {metalist-0.6.3 → metalist-0.7.1}/app/db/file_schema.py +0 -0
  41. {metalist-0.6.3 → metalist-0.7.1}/app/db/file_session.py +0 -0
  42. {metalist-0.6.3 → metalist-0.7.1}/app/db/files_sql.py +0 -0
  43. {metalist-0.6.3 → metalist-0.7.1}/app/db/link_titles_sql.py +0 -0
  44. {metalist-0.6.3 → metalist-0.7.1}/app/db/live_recovery.py +0 -0
  45. {metalist-0.6.3 → metalist-0.7.1}/app/db/migrations.py +0 -0
  46. {metalist-0.6.3 → metalist-0.7.1}/app/db/notes_sql.py +0 -0
  47. {metalist-0.6.3 → metalist-0.7.1}/app/db/ontology_rules_sql.py +0 -0
  48. {metalist-0.6.3 → metalist-0.7.1}/app/db/reminders_sql.py +0 -0
  49. {metalist-0.6.3 → metalist-0.7.1}/app/db/retire_sounds.py +0 -0
  50. {metalist-0.6.3 → metalist-0.7.1}/app/db/schema.py +0 -0
  51. {metalist-0.6.3 → metalist-0.7.1}/app/db/search_history_sql.py +0 -0
  52. {metalist-0.6.3 → metalist-0.7.1}/app/db/session.py +0 -0
  53. {metalist-0.6.3 → metalist-0.7.1}/app/db/settings_sql.py +0 -0
  54. {metalist-0.6.3 → metalist-0.7.1}/app/db/tab_state_sql.py +0 -0
  55. {metalist-0.6.3 → metalist-0.7.1}/app/db/version.py +0 -0
  56. {metalist-0.6.3 → metalist-0.7.1}/app/encryption_audit.py +0 -0
  57. {metalist-0.6.3 → metalist-0.7.1}/app/https_proxy.py +0 -0
  58. {metalist-0.6.3 → metalist-0.7.1}/app/main.py +0 -0
  59. {metalist-0.6.3 → metalist-0.7.1}/app/models/commands.py +0 -0
  60. {metalist-0.6.3 → metalist-0.7.1}/app/models/database.py +0 -0
  61. {metalist-0.6.3 → metalist-0.7.1}/app/models/entities.py +0 -0
  62. {metalist-0.6.3 → metalist-0.7.1}/app/models/enums.py +0 -0
  63. {metalist-0.6.3 → metalist-0.7.1}/app/models/linked_list.py +0 -0
  64. {metalist-0.6.3 → metalist-0.7.1}/app/models/list_operations.py +0 -0
  65. {metalist-0.6.3 → metalist-0.7.1}/app/models/list_traversal.py +0 -0
  66. {metalist-0.6.3 → metalist-0.7.1}/app/models/note_crud.py +0 -0
  67. {metalist-0.6.3 → metalist-0.7.1}/app/models/utils.py +0 -0
  68. {metalist-0.6.3 → metalist-0.7.1}/app/presentation/__init__.py +0 -0
  69. {metalist-0.6.3 → metalist-0.7.1}/app/presentation/render/__init__.py +0 -0
  70. {metalist-0.6.3 → metalist-0.7.1}/app/presentation/render/note_renderer.py +0 -0
  71. {metalist-0.6.3 → metalist-0.7.1}/app/presentation/templates.py +0 -0
  72. {metalist-0.6.3 → metalist-0.7.1}/app/security/authenticated_logging.py +0 -0
  73. {metalist-0.6.3 → metalist-0.7.1}/app/security/encryption.py +0 -0
  74. {metalist-0.6.3 → metalist-0.7.1}/app/security/http_headers.py +0 -0
  75. {metalist-0.6.3 → metalist-0.7.1}/app/security/note_html.py +0 -0
  76. {metalist-0.6.3 → metalist-0.7.1}/app/security/sensitive_cache.py +0 -0
  77. {metalist-0.6.3 → metalist-0.7.1}/app/security/sensitive_logging.py +0 -0
  78. {metalist-0.6.3 → metalist-0.7.1}/app/security/shell_execution.py +0 -0
  79. {metalist-0.6.3 → metalist-0.7.1}/app/security/validation_errors.py +0 -0
  80. {metalist-0.6.3 → metalist-0.7.1}/app/server_runtime.py +0 -0
  81. {metalist-0.6.3 → metalist-0.7.1}/app/services/__init__.py +0 -0
  82. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/__init__.py +0 -0
  83. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/actions.py +0 -0
  84. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/cloud_privacy.py +0 -0
  85. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/context.py +0 -0
  86. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/evidence_serialization.py +0 -0
  87. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/inference.py +0 -0
  88. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/investigation.py +0 -0
  89. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/model_policy.py +0 -0
  90. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/ollama_inference.py +0 -0
  91. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/openai_cost_tracking.py +0 -0
  92. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/openai_inference.py +0 -0
  93. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/permissions.py +0 -0
  94. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/prompt_settings.py +0 -0
  95. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/prompts/__init__.py +0 -0
  96. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/prompts/final-response.md +0 -0
  97. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/prompts/system.md +0 -0
  98. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/prompts/tool-result.md +0 -0
  99. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/retrieval_settings.py +0 -0
  100. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/runtime.py +0 -0
  101. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/scope.py +0 -0
  102. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/skill_settings.py +0 -0
  103. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/skills/__init__.py +0 -0
  104. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/skills/scoped-investigation.md +0 -0
  105. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/tagging.py +0 -0
  106. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/tagging_run.py +0 -0
  107. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/token_estimation.py +0 -0
  108. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/tools.py +0 -0
  109. {metalist-0.6.3 → metalist-0.7.1}/app/services/agent/trace.py +0 -0
  110. {metalist-0.6.3 → metalist-0.7.1}/app/services/ai_chat.py +0 -0
  111. {metalist-0.6.3 → metalist-0.7.1}/app/services/ai_chat_rendering.py +0 -0
  112. {metalist-0.6.3 → metalist-0.7.1}/app/services/ai_chat_stream.py +0 -0
  113. {metalist-0.6.3 → metalist-0.7.1}/app/services/app_update_worker.py +0 -0
  114. {metalist-0.6.3 → metalist-0.7.1}/app/services/app_updates.py +0 -0
  115. {metalist-0.6.3 → metalist-0.7.1}/app/services/auth.py +0 -0
  116. {metalist-0.6.3 → metalist-0.7.1}/app/services/auth_cache_state.py +0 -0
  117. {metalist-0.6.3 → metalist-0.7.1}/app/services/auth_service.py +0 -0
  118. {metalist-0.6.3 → metalist-0.7.1}/app/services/backlink_index.py +0 -0
  119. {metalist-0.6.3 → metalist-0.7.1}/app/services/backlinks.py +0 -0
  120. {metalist-0.6.3 → metalist-0.7.1}/app/services/backup_service.py +0 -0
  121. {metalist-0.6.3 → metalist-0.7.1}/app/services/backup_settings_service.py +0 -0
  122. {metalist-0.6.3 → metalist-0.7.1}/app/services/bulk_operation.py +0 -0
  123. {metalist-0.6.3 → metalist-0.7.1}/app/services/client_state_service.py +0 -0
  124. {metalist-0.6.3 → metalist-0.7.1}/app/services/content_cache.py +0 -0
  125. {metalist-0.6.3 → metalist-0.7.1}/app/services/content_formatting.py +0 -0
  126. {metalist-0.6.3 → metalist-0.7.1}/app/services/diagnostics.py +0 -0
  127. {metalist-0.6.3 → metalist-0.7.1}/app/services/embedded_references.py +0 -0
  128. {metalist-0.6.3 → metalist-0.7.1}/app/services/encryption.py +0 -0
  129. {metalist-0.6.3 → metalist-0.7.1}/app/services/exception_capture.py +0 -0
  130. {metalist-0.6.3 → metalist-0.7.1}/app/services/file_registry.py +0 -0
  131. {metalist-0.6.3 → metalist-0.7.1}/app/services/file_storage.py +0 -0
  132. {metalist-0.6.3 → metalist-0.7.1}/app/services/footnote_layout.py +0 -0
  133. {metalist-0.6.3 → metalist-0.7.1}/app/services/footnote_rendering.py +0 -0
  134. {metalist-0.6.3 → metalist-0.7.1}/app/services/hierarchy.py +0 -0
  135. {metalist-0.6.3 → metalist-0.7.1}/app/services/html_export.py +0 -0
  136. {metalist-0.6.3 → metalist-0.7.1}/app/services/html_unformatting.py +0 -0
  137. {metalist-0.6.3 → metalist-0.7.1}/app/services/hydration_state.py +0 -0
  138. {metalist-0.6.3 → metalist-0.7.1}/app/services/image_size_formatting.py +0 -0
  139. {metalist-0.6.3 → metalist-0.7.1}/app/services/inline_image_occurrences.py +0 -0
  140. {metalist-0.6.3 → metalist-0.7.1}/app/services/input_errors.py +0 -0
  141. {metalist-0.6.3 → metalist-0.7.1}/app/services/integrity.py +0 -0
  142. {metalist-0.6.3 → metalist-0.7.1}/app/services/latex_rendering.py +0 -0
  143. {metalist-0.6.3 → metalist-0.7.1}/app/services/link_titles.py +0 -0
  144. {metalist-0.6.3 → metalist-0.7.1}/app/services/login_rate_limit.py +0 -0
  145. {metalist-0.6.3 → metalist-0.7.1}/app/services/maintenance_mode.py +0 -0
  146. {metalist-0.6.3 → metalist-0.7.1}/app/services/managed_ollama_runtime.py +0 -0
  147. {metalist-0.6.3 → metalist-0.7.1}/app/services/markdown_rendering.py +0 -0
  148. {metalist-0.6.3 → metalist-0.7.1}/app/services/namespace_deletion_jobs.py +0 -0
  149. {metalist-0.6.3 → metalist-0.7.1}/app/services/namespace_deletion_worker.py +0 -0
  150. {metalist-0.6.3 → metalist-0.7.1}/app/services/namespace_rename_jobs.py +0 -0
  151. {metalist-0.6.3 → metalist-0.7.1}/app/services/namespace_rename_worker.py +0 -0
  152. {metalist-0.6.3 → metalist-0.7.1}/app/services/namespace_runtime_guard.py +0 -0
  153. {metalist-0.6.3 → metalist-0.7.1}/app/services/namespace_switcher.py +0 -0
  154. {metalist-0.6.3 → metalist-0.7.1}/app/services/note_fullscreen.py +0 -0
  155. {metalist-0.6.3 → metalist-0.7.1}/app/services/note_image_tags.py +0 -0
  156. {metalist-0.6.3 → metalist-0.7.1}/app/services/note_ordering.py +0 -0
  157. {metalist-0.6.3 → metalist-0.7.1}/app/services/ollama_provider.py +0 -0
  158. {metalist-0.6.3 → metalist-0.7.1}/app/services/ontology_rules_store.py +0 -0
  159. {metalist-0.6.3 → metalist-0.7.1}/app/services/openai_credentials.py +0 -0
  160. {metalist-0.6.3 → metalist-0.7.1}/app/services/password_note_fields.py +0 -0
  161. {metalist-0.6.3 → metalist-0.7.1}/app/services/reference_presentation.py +0 -0
  162. {metalist-0.6.3 → metalist-0.7.1}/app/services/reminders.py +0 -0
  163. {metalist-0.6.3 → metalist-0.7.1}/app/services/remote_image_proxy.py +0 -0
  164. {metalist-0.6.3 → metalist-0.7.1}/app/services/request_recovery.py +0 -0
  165. {metalist-0.6.3 → metalist-0.7.1}/app/services/resource_limits.py +0 -0
  166. {metalist-0.6.3 → metalist-0.7.1}/app/services/root_sorting.py +0 -0
  167. {metalist-0.6.3 → metalist-0.7.1}/app/services/runtime_generation.py +0 -0
  168. {metalist-0.6.3 → metalist-0.7.1}/app/services/runtime_hardening.py +0 -0
  169. {metalist-0.6.3 → metalist-0.7.1}/app/services/runtime_lock.py +0 -0
  170. {metalist-0.6.3 → metalist-0.7.1}/app/services/search_history.py +0 -0
  171. {metalist-0.6.3 → metalist-0.7.1}/app/services/search_history_storage.py +0 -0
  172. {metalist-0.6.3 → metalist-0.7.1}/app/services/search_index.py +0 -0
  173. {metalist-0.6.3 → metalist-0.7.1}/app/services/search_query.py +0 -0
  174. {metalist-0.6.3 → metalist-0.7.1}/app/services/search_text.py +0 -0
  175. {metalist-0.6.3 → metalist-0.7.1}/app/services/selected_text_tag.py +0 -0
  176. {metalist-0.6.3 → metalist-0.7.1}/app/services/self_update.py +0 -0
  177. {metalist-0.6.3 → metalist-0.7.1}/app/services/session_timeout_service.py +0 -0
  178. {metalist-0.6.3 → metalist-0.7.1}/app/services/shell_session_service.py +0 -0
  179. {metalist-0.6.3 → metalist-0.7.1}/app/services/snapshot.py +0 -0
  180. {metalist-0.6.3 → metalist-0.7.1}/app/services/store.py +0 -0
  181. {metalist-0.6.3 → metalist-0.7.1}/app/services/structured_note_renderers.py +0 -0
  182. {metalist-0.6.3 → metalist-0.7.1}/app/services/sync.py +0 -0
  183. {metalist-0.6.3 → metalist-0.7.1}/app/services/sync_state.py +0 -0
  184. {metalist-0.6.3 → metalist-0.7.1}/app/services/tab_state.py +0 -0
  185. {metalist-0.6.3 → metalist-0.7.1}/app/services/tag_case.py +0 -0
  186. {metalist-0.6.3 → metalist-0.7.1}/app/services/tag_ontology.py +0 -0
  187. {metalist-0.6.3 → metalist-0.7.1}/app/services/tag_rename.py +0 -0
  188. {metalist-0.6.3 → metalist-0.7.1}/app/services/tag_suggestions.py +0 -0
  189. {metalist-0.6.3 → metalist-0.7.1}/app/services/tag_term_matching.py +0 -0
  190. {metalist-0.6.3 → metalist-0.7.1}/app/services/test_reset.py +0 -0
  191. {metalist-0.6.3 → metalist-0.7.1}/app/services/tokens.py +0 -0
  192. {metalist-0.6.3 → metalist-0.7.1}/app/services/undo_state.py +0 -0
  193. {metalist-0.6.3 → metalist-0.7.1}/app/services/update_backups.py +0 -0
  194. {metalist-0.6.3 → metalist-0.7.1}/app/services/update_preflight.py +0 -0
  195. {metalist-0.6.3 → metalist-0.7.1}/app/services/view_cache.py +0 -0
  196. {metalist-0.6.3 → metalist-0.7.1}/app/services/view_diff.py +0 -0
  197. {metalist-0.6.3 → metalist-0.7.1}/app/services/view_state.py +0 -0
  198. {metalist-0.6.3 → metalist-0.7.1}/app/services/windows_process_control.py +0 -0
  199. {metalist-0.6.3 → metalist-0.7.1}/app/startup_environment.py +0 -0
  200. {metalist-0.6.3 → metalist-0.7.1}/app/startup_js_sanity.py +0 -0
  201. {metalist-0.6.3 → metalist-0.7.1}/app/startup_sanity.py +0 -0
  202. {metalist-0.6.3 → metalist-0.7.1}/app/startup_sanity_config.py +0 -0
  203. {metalist-0.6.3 → metalist-0.7.1}/app/static/config/command_palette_tags.json +0 -0
  204. {metalist-0.6.3 → metalist-0.7.1}/app/static/css/floating-notes.css +0 -0
  205. {metalist-0.6.3 → metalist-0.7.1}/app/static/css/forms.css +0 -0
  206. {metalist-0.6.3 → metalist-0.7.1}/app/static/img/favicon.ico +0 -0
  207. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/locked.js +0 -0
  208. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/main.js +0 -0
  209. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/activity-tracker.js +0 -0
  210. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/ai-chat/agent-prompt-service.js +0 -0
  211. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/ai-chat/agent-retrieval-settings.js +0 -0
  212. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/ai-chat/ai-agent-debug-view.js +0 -0
  213. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/ai-chat/ai-chat-api.js +0 -0
  214. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/ai-chat/ai-chat-panel-controller.js +0 -0
  215. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/ai-chat/ai-chat-panel-service.js +0 -0
  216. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/ai-chat/ai-thinking-level-service.js +0 -0
  217. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/ai-chat/bulk-elapsed-clock.js +0 -0
  218. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/ai-chat/bulk-proposal-ui.js +0 -0
  219. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/ai-chat/cloud-privacy-policy.js +0 -0
  220. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/ai-chat/proposal-menu.js +0 -0
  221. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/api-client.js +0 -0
  222. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/api-failure-classification-service.js +0 -0
  223. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/app-update-service.js +0 -0
  224. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/application-state.js +0 -0
  225. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/async-result.js +0 -0
  226. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/auth.js +0 -0
  227. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/client-state-api.js +0 -0
  228. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/client-state-migration.js +0 -0
  229. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/command-palette/activation-auth-policy.js +0 -0
  230. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/command-palette/backup-command-availability.js +0 -0
  231. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
  232. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/command-palette/command-palette-controller.js +0 -0
  233. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/command-palette/endpoint-registry.js +0 -0
  234. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/command-palette/note-layout-preferences.js +0 -0
  235. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/command-palette/preferences-store.js +0 -0
  236. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/command-palette/tag-config-loader.js +0 -0
  237. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/command-palette/usage-store.js +0 -0
  238. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/config.js +0 -0
  239. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/connectivity-monitor.js +0 -0
  240. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/context-menu/context-menu-registry.js +0 -0
  241. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/context-menu/context-menu-service.js +0 -0
  242. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/context-menu/context-menu-target-service.js +0 -0
  243. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
  244. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/disconnect-editing-state-service.js +0 -0
  245. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/dom-utils-fixed.js +0 -0
  246. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/dom-utils.js +0 -0
  247. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/editor-commands.js +0 -0
  248. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/editor-selection.js +0 -0
  249. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/editor-toolbar.js +0 -0
  250. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/elapsed-time.js +0 -0
  251. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/error-handler.js +0 -0
  252. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/error-overlay.js +0 -0
  253. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/expected-errors.js +0 -0
  254. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/location-flags.js +0 -0
  255. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/login-namespace-picker.js +0 -0
  256. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/maintenance.js +0 -0
  257. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/agent-prompt-editor-modal.js +0 -0
  258. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/ai-agent-settings-modal.js +0 -0
  259. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
  260. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/backup-restore-modal.js +0 -0
  261. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/backup-result-modal.js +0 -0
  262. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
  263. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/backup-settings-modal.js +0 -0
  264. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/base-modal.js +0 -0
  265. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/confirmation-modal.js +0 -0
  266. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/delete-namespace-modal.js +0 -0
  267. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/delete-namespace-validation.js +0 -0
  268. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/help-modal.js +0 -0
  269. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/namespace-loading-page.js +0 -0
  270. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/namespace-modals.js +0 -0
  271. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/namespace-ports-profile.js +0 -0
  272. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/namespace-rename-validation.js +0 -0
  273. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/note-layout-appearance-modal.js +0 -0
  274. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/ontology-modal.js +0 -0
  275. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/password-modal.js +0 -0
  276. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/prioritize-modal.js +0 -0
  277. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/random-password-modal.js +0 -0
  278. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/reminder-modal.js +0 -0
  279. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
  280. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/search-suggestion-statistics-modal.js +0 -0
  281. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
  282. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/actions/content-actions.js +0 -0
  283. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
  284. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/actions/note-actions.js +0 -0
  285. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
  286. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
  287. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/actions/ui-actions.js +0 -0
  288. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/events/context-menu-events.js +0 -0
  289. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/events/focus-events.js +0 -0
  290. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
  291. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
  292. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/events/keyboard-events.js +0 -0
  293. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/events/mouse-events.js +0 -0
  294. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
  295. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/mode-context.js +0 -0
  296. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/mode-logger.js +0 -0
  297. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
  298. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/add-style-service.js +0 -0
  299. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
  300. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/backlinks-navigation-service.js +0 -0
  301. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
  302. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
  303. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
  304. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
  305. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -0
  306. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +0 -0
  307. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/deselect-selection-state-service.js +0 -0
  308. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/differential-view-service.js +0 -0
  309. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
  310. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
  311. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
  312. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/file-drop-policy-service.js +0 -0
  313. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
  314. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
  315. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
  316. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/floating-note-dom.js +0 -0
  317. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/floating-note-layout.js +0 -0
  318. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/floating-note-service.js +0 -0
  319. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/footnote-navigation-service.js +0 -0
  320. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/history-selection-policy-service.js +0 -0
  321. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/history-shortcut-policy-service.js +0 -0
  322. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +0 -0
  323. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +0 -0
  324. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +0 -0
  325. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
  326. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
  327. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/link-title-prefetch-service.js +0 -0
  328. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
  329. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/mermaid-render-service.js +0 -0
  330. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
  331. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
  332. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
  333. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
  334. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/note-delete-shortcut-policy-service.js +0 -0
  335. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
  336. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/note-fullscreen-service.js +0 -0
  337. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
  338. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
  339. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/note-timestamp-hover-service.js +0 -0
  340. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
  341. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
  342. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/reference-source-navigation-service.js +0 -0
  343. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/remote-image-proxy-service.js +0 -0
  344. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/remove-formatting-service.js +0 -0
  345. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
  346. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
  347. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
  348. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
  349. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
  350. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
  351. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
  352. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
  353. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
  354. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
  355. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
  356. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/search-suggestion-statistics-service.js +0 -0
  357. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/search-suggestion-window-labels-service.js +0 -0
  358. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/search-suggestion-windows-service.js +0 -0
  359. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +0 -0
  360. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
  361. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/selected-formatting-removal-service.js +0 -0
  362. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/selected-text-tag-service.js +0 -0
  363. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
  364. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
  365. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/tab-drag-service.js +0 -0
  366. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
  367. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/tag-bar-service.js +0 -0
  368. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/tag-input-selection-service.js +0 -0
  369. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/tag-proposal-service.js +0 -0
  370. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +0 -0
  371. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +0 -0
  372. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
  373. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/untagged-view-indicator-service.js +0 -0
  374. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
  375. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/note-html-sanitizer.js +0 -0
  376. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/password-generator.js +0 -0
  377. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/password-operation-response.js +0 -0
  378. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/password-policy.js +0 -0
  379. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/password-strength.js +0 -0
  380. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/reminder-store.js +0 -0
  381. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/reminder-surface-service.js +0 -0
  382. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/selection-markers.js +0 -0
  383. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/session-auth.js +0 -0
  384. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/tag-token.js +0 -0
  385. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/modules/uuid.js +0 -0
  386. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/namespace-deleted.js +0 -0
  387. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/namespace-renamed.js +0 -0
  388. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/vendor/dompurify-3.4.13.LICENSE.txt +0 -0
  389. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/vendor/dompurify-3.4.13.min.js +0 -0
  390. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/vendor/markdown-it-14.3.2.LICENSE.txt +0 -0
  391. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/vendor/markdown-it-14.3.2.min.js +0 -0
  392. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/vendor/mermaid-11.16.1.LICENSE.txt +0 -0
  393. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/vendor/mermaid-11.16.1.min.js +0 -0
  394. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/vendor/zxcvbn-4.4.2.LICENSE.txt +0 -0
  395. {metalist-0.6.3 → metalist-0.7.1}/app/static/js/vendor/zxcvbn-4.4.2.js +0 -0
  396. {metalist-0.6.3 → metalist-0.7.1}/app/static/note-html-policy.json +0 -0
  397. {metalist-0.6.3 → metalist-0.7.1}/app/static/video/login-startup.mp4 +0 -0
  398. {metalist-0.6.3 → metalist-0.7.1}/app/templates/base.html +0 -0
  399. {metalist-0.6.3 → metalist-0.7.1}/app/templates/index.html +0 -0
  400. {metalist-0.6.3 → metalist-0.7.1}/app/templates/locked.html +0 -0
  401. {metalist-0.6.3 → metalist-0.7.1}/app/templates/maintenance.html +0 -0
  402. {metalist-0.6.3 → metalist-0.7.1}/app/templates/namespace_deleted.html +0 -0
  403. {metalist-0.6.3 → metalist-0.7.1}/app/templates/namespace_renamed.html +0 -0
  404. {metalist-0.6.3 → metalist-0.7.1}/app/templates/notes_list.html +0 -0
  405. {metalist-0.6.3 → metalist-0.7.1}/app/upload_limits.py +0 -0
  406. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/__init__.py +0 -0
  407. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/add_selected_text_tag.py +0 -0
  408. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/alphabetize_root_notes.py +0 -0
  409. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/base.py +0 -0
  410. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/bulk_tag_proposals.py +0 -0
  411. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/collapse.py +0 -0
  412. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/copy_note.py +0 -0
  413. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/create_child.py +0 -0
  414. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/create_note.py +0 -0
  415. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/create_sibling.py +0 -0
  416. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/delete_subtree.py +0 -0
  417. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/delete_tag.py +0 -0
  418. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/expand.py +0 -0
  419. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/indent.py +0 -0
  420. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/lock.py +0 -0
  421. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/move.py +0 -0
  422. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/move_to_top.py +0 -0
  423. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/outdent.py +0 -0
  424. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/paste_child.py +0 -0
  425. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/paste_sibling.py +0 -0
  426. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/prefetch_link_titles.py +0 -0
  427. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/prioritize.py +0 -0
  428. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/redo.py +0 -0
  429. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/rename_tag.py +0 -0
  430. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/reset_updated_at.py +0 -0
  431. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/resize_image.py +0 -0
  432. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/run_shell.py +0 -0
  433. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/search_comment_autofill.py +0 -0
  434. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/search_context_tags.py +0 -0
  435. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/set_collapse_bulk.py +0 -0
  436. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/set_collapse_in_context.py +0 -0
  437. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/split_note.py +0 -0
  438. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/tag_proposals.py +0 -0
  439. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/toggle_reference_mode.py +0 -0
  440. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/toggle_todo_done.py +0 -0
  441. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/undo.py +0 -0
  442. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/unformat_content.py +0 -0
  443. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/update_content.py +0 -0
  444. {metalist-0.6.3 → metalist-0.7.1}/app/usecases/view.py +0 -0
  445. {metalist-0.6.3 → metalist-0.7.1}/app/utils/__init__.py +0 -0
  446. {metalist-0.6.3 → metalist-0.7.1}/app/utils/encryption.py +0 -0
  447. {metalist-0.6.3 → metalist-0.7.1}/app/utils/text_utils.py +0 -0
  448. {metalist-0.6.3 → metalist-0.7.1}/convert-from-legacy.py +0 -0
  449. {metalist-0.6.3 → metalist-0.7.1}/metalist.egg-info/dependency_links.txt +0 -0
  450. {metalist-0.6.3 → metalist-0.7.1}/metalist.egg-info/entry_points.txt +0 -0
  451. {metalist-0.6.3 → metalist-0.7.1}/metalist.egg-info/requires.txt +0 -0
  452. {metalist-0.6.3 → metalist-0.7.1}/metalist.egg-info/top_level.txt +0 -0
  453. {metalist-0.6.3 → metalist-0.7.1}/pyproject.toml +0 -0
  454. {metalist-0.6.3 → metalist-0.7.1}/scripts/generate-lan-cert.sh +0 -0
  455. {metalist-0.6.3 → metalist-0.7.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metalist
3
- Version: 0.6.3
3
+ Version: 0.7.1
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
@@ -213,6 +213,18 @@ METALIST_HOST=192.168.1.20 metalist
213
213
  ```
214
214
  On a fresh machine, that first launch also creates the default TLS cert pair automatically. Then open either `http://192.168.1.20:8000` or `https://192.168.1.20:8443` from the other machine.
215
215
 
216
+ Starting with 0.7.1, an explicit `METALIST_HOST` or `METALIST_ALLOWED_HOSTS`
217
+ configuration is saved in `~/MetaList/network-settings.json` (under
218
+ `METALIST_DATA_DIRECTORY` when overridden). Subsequent source launches, plain
219
+ `metalist` launches, and updates reuse it. Environment values override and update
220
+ the corresponding saved settings. This file stores only the bind address and
221
+ allowed hosts; namespace ports remain in their launch profiles, and shell access
222
+ still requires `--enable-shell` on each launch. Invalid settings stop startup
223
+ with an error. Fresh installations continue to bind to localhost by default.
224
+ To switch back to local-only access, run `METALIST_HOST=127.0.0.1 metalist` once.
225
+ Older installed versions do not read this file; keep supplying their LAN settings
226
+ until upgrading to 0.7.1 or later.
227
+
216
228
  To keep `@shell` available on the host laptop while allowing another laptop to
217
229
  use normal MetaList features, bind both interfaces and explicitly allow the LAN
218
230
  address:
@@ -177,6 +177,18 @@ METALIST_HOST=192.168.1.20 metalist
177
177
  ```
178
178
  On a fresh machine, that first launch also creates the default TLS cert pair automatically. Then open either `http://192.168.1.20:8000` or `https://192.168.1.20:8443` from the other machine.
179
179
 
180
+ Starting with 0.7.1, an explicit `METALIST_HOST` or `METALIST_ALLOWED_HOSTS`
181
+ configuration is saved in `~/MetaList/network-settings.json` (under
182
+ `METALIST_DATA_DIRECTORY` when overridden). Subsequent source launches, plain
183
+ `metalist` launches, and updates reuse it. Environment values override and update
184
+ the corresponding saved settings. This file stores only the bind address and
185
+ allowed hosts; namespace ports remain in their launch profiles, and shell access
186
+ still requires `--enable-shell` on each launch. Invalid settings stop startup
187
+ with an error. Fresh installations continue to bind to localhost by default.
188
+ To switch back to local-only access, run `METALIST_HOST=127.0.0.1 metalist` once.
189
+ Older installed versions do not read this file; keep supplying their LAN settings
190
+ until upgrading to 0.7.1 or later.
191
+
180
192
  To keep `@shell` available on the host laptop while allowing another laptop to
181
193
  use normal MetaList features, bind both interfaces and explicitly allow the LAN
182
194
  address:
@@ -78,9 +78,9 @@ CAPTURE_BOUNDARIES = {
78
78
  # Translate explicitly validated domain input/resource rejection.
79
79
  'app/main.py:namespace_renamed_page:job_record_capture': ('NamespaceInputRejected', 'FileNotFoundError'),
80
80
  # Parse external host, URL, IP or date input at the named boundary.
81
- 'app/security/request_boundary.py:_normalize_configured_hostname:normalize_capture': ('ValueError',),
81
+ 'app/security/request_hosts.py:_normalize_configured_hostname:normalize_capture': ('ValueError',),
82
82
  # Parse external host, URL, IP or date input at the named boundary.
83
- 'app/security/request_boundary.py:_normalize_hostname:ip_capture': ('ValueError',),
83
+ 'app/security/request_hosts.py:_normalize_hostname:ip_capture': ('ValueError',),
84
84
  # Parse external host, URL, IP or date input at the named boundary.
85
85
  'app/security/request_boundary.py:_parse_host_header:parse_capture': ('ValueError',),
86
86
  # Parse external host, URL, IP or date input at the named boundary.
@@ -0,0 +1,61 @@
1
+ """Machine network preferences shared by source and installed entrypoints.
2
+
3
+ Only an explicit bind address/allowed-host configuration opts into persistence.
4
+ Namespace ports, shell capability, credentials, and backups are not stored here.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from collections.abc import MutableMapping
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+ from uuid import uuid4
13
+
14
+ from app.data_directory import resolve_data_directory
15
+ from app.security.request_hosts import resolve_allowed_request_hosts
16
+
17
+
18
+ _NETWORK_ENVIRONMENT_KEYS = frozenset({'METALIST_HOST', 'METALIST_ALLOWED_HOSTS'})
19
+
20
+
21
+ def _validate_network_environment(environment: object) -> dict[str, str]:
22
+ if not isinstance(environment, dict) or not environment.keys() <= _NETWORK_ENVIRONMENT_KEYS:
23
+ raise RuntimeError('Network settings must contain only bind-address and allowed-host preferences')
24
+ if any(not isinstance(value, str) for value in environment.values()):
25
+ raise RuntimeError('Network settings values must be strings')
26
+ resolve_allowed_request_hosts(environ=environment)
27
+ return dict(environment)
28
+
29
+
30
+ def _read_network_settings(path: Path) -> dict[str, str]:
31
+ if not path.exists():
32
+ return {}
33
+ payload = json.loads(path.read_text(encoding='utf-8'))
34
+ if (not isinstance(payload, dict) or set(payload) != {'version', 'environment'}
35
+ or type(payload['version']) is not int or payload['version'] != 1):
36
+ raise RuntimeError(f'Invalid network settings file: {path}')
37
+ return _validate_network_environment(payload['environment'])
38
+
39
+
40
+ def _write_network_settings(path: Path, environment: dict[str, str]) -> None:
41
+ assert path.name == 'network-settings.json'
42
+ path.parent.mkdir(parents=True, exist_ok=True)
43
+ temporary = path.with_name(f'.network-settings-{uuid4().hex}.tmp')
44
+ try:
45
+ with temporary.open('x', encoding='utf-8') as stream:
46
+ json.dump({'version': 1, 'environment': environment}, stream, indent=2)
47
+ stream.write('\n')
48
+ os.replace(temporary, path)
49
+ finally:
50
+ temporary.unlink(missing_ok=True)
51
+
52
+
53
+ def apply_network_settings(*, environ: MutableMapping[str, str], persist: bool) -> None:
54
+ path = resolve_data_directory(environ=environ) / 'network-settings.json'
55
+ saved = _read_network_settings(path)
56
+ configured = {key: environ[key] for key in _NETWORK_ENVIRONMENT_KEYS if key in environ}
57
+ effective = _validate_network_environment(saved | configured)
58
+ if persist and effective != saved:
59
+ _write_network_settings(path, effective)
60
+ print(f'[startup] Saved network settings: {path}', flush=True)
61
+ environ.update(effective)
@@ -1,9 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Mapping
4
3
  from dataclasses import dataclass
5
- import ipaddress
6
- import re
7
4
  from urllib.parse import urlsplit
8
5
 
9
6
  from fastapi import Request
@@ -12,14 +9,10 @@ from starlette.middleware.base import BaseHTTPMiddleware
12
9
 
13
10
  from app.api.request_auth import AUTH_COOKIE_NAME
14
11
  from app.services.exception_capture import CapturedExceptionContext
12
+ from app.security.request_hosts import _normalize_hostname, resolve_allowed_request_hosts
15
13
 
16
14
 
17
- _LOOPBACK_REQUEST_HOSTS = frozenset({"127.0.0.1", "::1", "localhost"})
18
- _WILDCARD_BIND_HOSTS = frozenset({"0.0.0.0", "::"})
19
15
  _SAFE_METHODS = frozenset({"GET", "HEAD", "OPTIONS"})
20
- _DNS_HOSTNAME_PATTERN = re.compile(
21
- r"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$"
22
- )
23
16
 
24
17
 
25
18
  @dataclass(frozen=True)
@@ -28,72 +21,6 @@ class RequestBoundaryRejection:
28
21
  detail: str
29
22
 
30
23
 
31
- def _normalize_hostname(*, hostname: str, context: str) -> str:
32
- normalized = hostname.strip().casefold().rstrip(".")
33
- if normalized == "":
34
- raise ValueError(f"{context} hostname must not be empty")
35
-
36
- ip_capture = CapturedExceptionContext(ValueError, boundary='app/security/request_boundary.py:_normalize_hostname:ip_capture')
37
- parsed_ip: ipaddress._BaseAddress | None = None
38
- with ip_capture:
39
- parsed_ip = ipaddress.ip_address(normalized)
40
- if ip_capture.captured_exception is None:
41
- if parsed_ip is None:
42
- raise RuntimeError("IP parser returned no address")
43
- return parsed_ip.compressed.casefold()
44
-
45
- if _DNS_HOSTNAME_PATTERN.fullmatch(normalized) is None:
46
- raise ValueError(f"{context} contains an invalid hostname: {hostname!r}")
47
- return normalized
48
-
49
-
50
- def _normalize_configured_hostname(*, raw_value: str) -> str:
51
- value = raw_value.strip()
52
- if value == "":
53
- raise RuntimeError("METALIST_ALLOWED_HOSTS contains an empty entry")
54
- if value == "*":
55
- raise RuntimeError("METALIST_ALLOWED_HOSTS must not contain '*'")
56
- if "://" in value or "/" in value or "@" in value:
57
- raise RuntimeError(
58
- "METALIST_ALLOWED_HOSTS entries must be hostnames without schemes, ports, or paths"
59
- )
60
-
61
- unbracketed_value = value
62
- if value.startswith("[") and value.endswith("]"):
63
- unbracketed_value = value[1:-1]
64
- normalize_capture = CapturedExceptionContext(ValueError, boundary='app/security/request_boundary.py:_normalize_configured_hostname:normalize_capture')
65
- normalized_hostname: str | None = None
66
- with normalize_capture:
67
- normalized_hostname = _normalize_hostname(
68
- hostname=unbracketed_value,
69
- context="METALIST_ALLOWED_HOSTS",
70
- )
71
- if normalize_capture.captured_exception is not None:
72
- exc = normalize_capture.captured_exception
73
- raise RuntimeError(str(exc)) from exc
74
- if normalized_hostname is None:
75
- raise RuntimeError("METALIST_ALLOWED_HOSTS parser returned no hostname")
76
- return normalized_hostname
77
-
78
-
79
- def resolve_allowed_request_hosts(*, environ: Mapping[str, str]) -> frozenset[str]:
80
- allowed_hosts = set(_LOOPBACK_REQUEST_HOSTS)
81
- bind_host = environ.get("METALIST_HOST", "127.0.0.1").strip()
82
- if bind_host == "":
83
- raise RuntimeError("METALIST_HOST must not be empty")
84
- normalized_bind_host = _normalize_configured_hostname(raw_value=bind_host)
85
- if normalized_bind_host not in _WILDCARD_BIND_HOSTS:
86
- allowed_hosts.add(normalized_bind_host)
87
-
88
- configured_hosts = environ.get("METALIST_ALLOWED_HOSTS")
89
- if configured_hosts is not None:
90
- if configured_hosts.strip() == "":
91
- raise RuntimeError("METALIST_ALLOWED_HOSTS must not be empty when set")
92
- for raw_host in configured_hosts.split(","):
93
- allowed_hosts.add(_normalize_configured_hostname(raw_value=raw_host))
94
- return frozenset(allowed_hosts)
95
-
96
-
97
24
  def _parse_host_header(host_header: str) -> tuple[str, int | None] | None:
98
25
  raw_host = host_header.strip()
99
26
  if raw_host == "":
@@ -0,0 +1,83 @@
1
+ """Host validation shared by early launch settings and HTTP request checks."""
2
+ from __future__ import annotations
3
+
4
+ from collections.abc import Mapping
5
+ import ipaddress
6
+ import re
7
+
8
+ from app.services.exception_capture import CapturedExceptionContext
9
+
10
+
11
+ _LOOPBACK_REQUEST_HOSTS = frozenset({"127.0.0.1", "::1", "localhost"})
12
+ _WILDCARD_BIND_HOSTS = frozenset({"0.0.0.0", "::"})
13
+ _DNS_HOSTNAME_PATTERN = re.compile(
14
+ r"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$"
15
+ )
16
+
17
+
18
+ def _normalize_hostname(*, hostname: str, context: str) -> str:
19
+ normalized = hostname.strip().casefold().rstrip(".")
20
+ if normalized == "":
21
+ raise ValueError(f"{context} hostname must not be empty")
22
+
23
+ ip_capture = CapturedExceptionContext(ValueError, boundary='app/security/request_hosts.py:_normalize_hostname:ip_capture')
24
+ parsed_ip: ipaddress._BaseAddress | None = None
25
+ with ip_capture:
26
+ parsed_ip = ipaddress.ip_address(normalized)
27
+ if ip_capture.captured_exception is None:
28
+ if parsed_ip is None:
29
+ raise RuntimeError("IP parser returned no address")
30
+ return parsed_ip.compressed.casefold()
31
+
32
+ if _DNS_HOSTNAME_PATTERN.fullmatch(normalized) is None:
33
+ raise ValueError(f"{context} contains an invalid hostname: {hostname!r}")
34
+ return normalized
35
+
36
+
37
+ def _normalize_configured_hostname(*, raw_value: str) -> str:
38
+ value = raw_value.strip()
39
+ if value == "":
40
+ raise RuntimeError("METALIST_ALLOWED_HOSTS contains an empty entry")
41
+ if value == "*":
42
+ raise RuntimeError("METALIST_ALLOWED_HOSTS must not contain '*'")
43
+ if "://" in value or "/" in value or "@" in value:
44
+ raise RuntimeError(
45
+ "METALIST_ALLOWED_HOSTS entries must be hostnames without schemes, ports, or paths"
46
+ )
47
+
48
+ unbracketed_value = value
49
+ if value.startswith("[") and value.endswith("]"):
50
+ unbracketed_value = value[1:-1]
51
+ normalize_capture = CapturedExceptionContext(ValueError, boundary='app/security/request_hosts.py:_normalize_configured_hostname:normalize_capture')
52
+ normalized_hostname: str | None = None
53
+ with normalize_capture:
54
+ normalized_hostname = _normalize_hostname(
55
+ hostname=unbracketed_value,
56
+ context="METALIST_ALLOWED_HOSTS",
57
+ )
58
+ if normalize_capture.captured_exception is not None:
59
+ exc = normalize_capture.captured_exception
60
+ raise RuntimeError(str(exc)) from exc
61
+ if normalized_hostname is None:
62
+ raise RuntimeError("METALIST_ALLOWED_HOSTS parser returned no hostname")
63
+ return normalized_hostname
64
+
65
+
66
+ def resolve_allowed_request_hosts(*, environ: Mapping[str, str]) -> frozenset[str]:
67
+ allowed_hosts = set(_LOOPBACK_REQUEST_HOSTS)
68
+ bind_host = environ.get("METALIST_HOST", "127.0.0.1").strip()
69
+ if bind_host == "":
70
+ raise RuntimeError("METALIST_HOST must not be empty")
71
+ normalized_bind_host = _normalize_configured_hostname(raw_value=bind_host)
72
+ if normalized_bind_host not in _WILDCARD_BIND_HOSTS:
73
+ allowed_hosts.add(normalized_bind_host)
74
+
75
+ configured_hosts = environ.get("METALIST_ALLOWED_HOSTS")
76
+ if configured_hosts is not None:
77
+ if configured_hosts.strip() == "":
78
+ raise RuntimeError("METALIST_ALLOWED_HOSTS must not be empty when set")
79
+ for raw_host in configured_hosts.split(","):
80
+ allowed_hosts.add(_normalize_configured_hostname(raw_value=raw_host))
81
+ return frozenset(allowed_hosts)
82
+
83
+
@@ -113,6 +113,8 @@ class NoteStore:
113
113
  self._ordering = NoteOrdering()
114
114
  self._effective_non_meta_tag_terms: Dict[str, FrozenSet[str]] = {}
115
115
  self._effective_proposed_non_meta_tag_terms: Dict[str, FrozenSet[str]] = {}
116
+ self._subtree_non_meta_tag_terms: Dict[str, FrozenSet[str]] = {}
117
+ self._subtree_proposed_non_meta_tag_terms: Dict[str, FrozenSet[str]] = {}
116
118
  self._loaded = False
117
119
  self._revision = 0
118
120
  self._timing_enabled = True
@@ -120,17 +122,20 @@ class NoteStore:
120
122
  def _get_children_locked(self, parent_id: Optional[str]) -> List[str]:
121
123
  return self._ordering.children(self._note_map, parent_id)
122
124
 
123
- def _tag_dependencies_locked(self, note_id: str) -> set[str]:
124
- record = self._note_map[note_id]
125
- dependencies = set(self._backlink_index.get_target_ids(note_id)) & self._note_map.keys()
126
- if record.parent_id is not None:
127
- assert record.parent_id in self._note_map
128
- dependencies.add(record.parent_id)
129
- return dependencies
125
+ def _reference_targets_locked(self, note_id: str) -> set[str]:
126
+ return set(self._backlink_index.get_target_ids(note_id)) & self._note_map.keys()
127
+
128
+ def _referrers_locked(self, note_id: str) -> set[str]:
129
+ return self._backlink_index.get_counts(note_id).keys() & self._note_map.keys()
130
130
 
131
- def _tag_dependents_locked(self, note_id: str) -> set[str]:
132
- return (set(self._get_children_locked(note_id))
133
- | (self._backlink_index.get_counts(note_id).keys() & self._note_map.keys()))
131
+ def _inherited_tag_terms_locked(
132
+ self, note_id: str, effective: Dict[str, FrozenSet[str]], subtree: Dict[str, FrozenSet[str]],
133
+ ) -> FrozenSet[str]:
134
+ terms = frozenset().union(*(subtree[target] for target in self._reference_targets_locked(note_id)))
135
+ parent_id = self._note_map[note_id].parent_id
136
+ if parent_id is not None:
137
+ terms |= effective[parent_id]
138
+ return terms
134
139
 
135
140
  def _raw_tag_terms_locked(self, note_id: str) -> FrozenSet[str]:
136
141
  record = self._note_map[note_id]
@@ -153,20 +158,46 @@ class NoteStore:
153
158
  raise RuntimeError("Integrity failure: unreachable notes during tag inheritance")
154
159
  self._effective_non_meta_tag_terms.clear()
155
160
  self._effective_proposed_non_meta_tag_terms.clear()
161
+ self._subtree_non_meta_tag_terms.clear()
162
+ self._subtree_proposed_non_meta_tag_terms.clear()
163
+ self._propagate_subtree_tag_terms_locked(visited)
156
164
  return self._propagate_inherited_tag_terms_locked(visited)
157
165
 
158
- def _recompute_effective_tag_terms_locked(self, root_ids: Iterable[str]) -> Dict[str, FrozenSet[str]]:
159
- # Include both hierarchy descendants and referrers, transitively. Rebuilding
160
- # this closure from direct tags also removes stale tags from reference cycles.
166
+ def _recompute_effective_tag_terms_locked(
167
+ self, root_ids: Iterable[str], *, subtree_root_ids: Iterable[str],
168
+ ) -> Dict[str, FrozenSet[str]]:
169
+ roots = set(root_ids)
170
+ # Subtree contributions flow upward and across references, never through
171
+ # inherited parent tags. Include former parents after moves/deletions.
172
+ subtree_affected: set[str] = set()
173
+ pending = list(roots | set(subtree_root_ids))
174
+ effective_roots = set(roots)
175
+ while pending:
176
+ note_id = pending.pop()
177
+ assert note_id in self._note_map
178
+ if note_id in subtree_affected:
179
+ continue
180
+ subtree_affected.add(note_id)
181
+ referrers = self._referrers_locked(note_id)
182
+ effective_roots.update(referrers)
183
+ pending.extend(referrers - subtree_affected)
184
+ parent_id = self._note_map[note_id].parent_id
185
+ if parent_id is not None:
186
+ pending.append(parent_id)
187
+ self._assert_acyclic_parent_paths_locked(subtree_affected)
188
+ self._propagate_subtree_tag_terms_locked(subtree_affected)
189
+
190
+ # Ordinary inheritance flows down from changed notes and referrers only;
191
+ # siblings of an edited source descendant need no search-index update.
161
192
  affected: set[str] = set()
162
- pending = list(root_ids)
193
+ pending = list(effective_roots)
163
194
  while pending:
164
195
  note_id = pending.pop()
165
196
  assert note_id in self._note_map
166
197
  if note_id in affected:
167
198
  continue
168
199
  affected.add(note_id)
169
- pending.extend(self._tag_dependents_locked(note_id) - affected)
200
+ pending.extend(set(self._get_children_locked(note_id)) - affected)
170
201
  self._assert_acyclic_parent_paths_locked(affected)
171
202
  return self._propagate_inherited_tag_terms_locked(affected)
172
203
 
@@ -183,39 +214,64 @@ class NoteStore:
183
214
  visited.update(path)
184
215
 
185
216
  def _propagate_inherited_tag_terms_locked(self, affected: set[str]) -> Dict[str, FrozenSet[str]]:
186
- dependents = {note_id: set() for note_id in affected}
217
+ dependencies = {}
187
218
  for note_id in affected:
188
219
  record = self._note_map[note_id]
189
- accepted = record.non_meta_tag_terms
190
- proposed = record.proposed_non_meta_tag_terms
191
- for source_id in self._tag_dependencies_locked(note_id):
192
- if source_id in affected:
220
+ targets = self._reference_targets_locked(note_id)
221
+ self._effective_non_meta_tag_terms[note_id] = record.non_meta_tag_terms | frozenset().union(
222
+ *(self._subtree_non_meta_tag_terms[target] for target in targets))
223
+ self._effective_proposed_non_meta_tag_terms[note_id] = (
224
+ record.proposed_non_meta_tag_terms | frozenset().union(
225
+ *(self._subtree_proposed_non_meta_tag_terms[target] for target in targets)))
226
+ dependencies[note_id] = set()
227
+ if record.parent_id is not None:
228
+ dependencies[note_id].add(record.parent_id)
229
+ self._propagate_tag_dependencies_locked(
230
+ dependencies, self._effective_non_meta_tag_terms, self._effective_proposed_non_meta_tag_terms)
231
+ return {note_id: self._raw_tag_terms_locked(note_id) for note_id in affected}
232
+
233
+ def _propagate_subtree_tag_terms_locked(self, affected: set[str]) -> None:
234
+ dependencies = {}
235
+ for note_id in affected:
236
+ record = self._note_map[note_id]
237
+ self._subtree_non_meta_tag_terms[note_id] = record.non_meta_tag_terms
238
+ self._subtree_proposed_non_meta_tag_terms[note_id] = record.proposed_non_meta_tag_terms
239
+ dependencies[note_id] = (set(self._get_children_locked(note_id))
240
+ | self._reference_targets_locked(note_id))
241
+ self._propagate_tag_dependencies_locked(
242
+ dependencies, self._subtree_non_meta_tag_terms, self._subtree_proposed_non_meta_tag_terms)
243
+
244
+ @staticmethod
245
+ def _propagate_tag_dependencies_locked(
246
+ dependencies: Dict[str, set[str]],
247
+ accepted_terms: Dict[str, FrozenSet[str]], proposed_terms: Dict[str, FrozenSet[str]],
248
+ ) -> None:
249
+ # Callers reset the affected closure to direct contributions first, so
250
+ # cycles converge to the least fixed point even when tags are removed.
251
+ dependents: Dict[str, set[str]] = {note_id: set() for note_id in dependencies}
252
+ for note_id, sources in dependencies.items():
253
+ assert note_id in accepted_terms and note_id in proposed_terms
254
+ for source_id in sources:
255
+ if source_id in dependencies:
193
256
  dependents[source_id].add(note_id)
194
257
  else:
195
- accepted |= self._effective_non_meta_tag_terms[source_id]
196
- proposed |= self._effective_proposed_non_meta_tag_terms[source_id]
197
- self._effective_non_meta_tag_terms[note_id] = accepted
198
- self._effective_proposed_non_meta_tag_terms[note_id] = proposed
199
-
200
- pending = deque(affected)
201
- queued = set(affected)
258
+ accepted_terms[note_id] |= accepted_terms[source_id]
259
+ proposed_terms[note_id] |= proposed_terms[source_id]
260
+ pending = deque(dependencies)
261
+ queued = set(dependencies)
202
262
  while pending:
203
263
  source_id = pending.popleft()
204
264
  queued.remove(source_id)
205
265
  for note_id in dependents[source_id]:
206
- accepted = (self._effective_non_meta_tag_terms[note_id]
207
- | self._effective_non_meta_tag_terms[source_id])
208
- proposed = (self._effective_proposed_non_meta_tag_terms[note_id]
209
- | self._effective_proposed_non_meta_tag_terms[source_id])
210
- if (accepted == self._effective_non_meta_tag_terms[note_id]
211
- and proposed == self._effective_proposed_non_meta_tag_terms[note_id]):
266
+ accepted = accepted_terms[note_id] | accepted_terms[source_id]
267
+ proposed = proposed_terms[note_id] | proposed_terms[source_id]
268
+ if accepted == accepted_terms[note_id] and proposed == proposed_terms[note_id]:
212
269
  continue
213
- self._effective_non_meta_tag_terms[note_id] = accepted
214
- self._effective_proposed_non_meta_tag_terms[note_id] = proposed
270
+ accepted_terms[note_id] = accepted
271
+ proposed_terms[note_id] = proposed
215
272
  if note_id not in queued:
216
273
  pending.append(note_id)
217
274
  queued.add(note_id)
218
- return {note_id: self._raw_tag_terms_locked(note_id) for note_id in affected}
219
275
 
220
276
  def _publish_tag_updates(self, raw_terms_by_id: Dict[str, FrozenSet[str]]) -> None:
221
277
  if not raw_terms_by_id:
@@ -242,6 +298,8 @@ class NoteStore:
242
298
  self._ordering = NoteOrdering()
243
299
  self._effective_non_meta_tag_terms.clear()
244
300
  self._effective_proposed_non_meta_tag_terms.clear()
301
+ self._subtree_non_meta_tag_terms.clear()
302
+ self._subtree_proposed_non_meta_tag_terms.clear()
245
303
  self._loaded = False
246
304
  search_index.rebuild(
247
305
  [],
@@ -625,7 +683,7 @@ class NoteStore:
625
683
  self._backlink_index.upsert(record.id, record.content, record.tags)
626
684
  self._insert_link(record.parent_id, record.id, record.prev_id, record.next_id)
627
685
 
628
- tag_updates = self._recompute_effective_tag_terms_locked({record.id})
686
+ tag_updates = self._recompute_effective_tag_terms_locked({record.id}, subtree_root_ids=())
629
687
  effective_tag_terms = tag_updates.pop(record.id)
630
688
 
631
689
  ontology = get_ontology()
@@ -699,7 +757,8 @@ class NoteStore:
699
757
  tag_sources_changed = True
700
758
 
701
759
  if tag_sources_changed:
702
- effective_tag_terms_by_id = self._recompute_effective_tag_terms_locked({note.id})
760
+ effective_tag_terms_by_id = self._recompute_effective_tag_terms_locked(
761
+ {note.id}, subtree_root_ids=())
703
762
  else:
704
763
  effective_tag_terms = self._raw_tag_terms_locked(updated.id)
705
764
 
@@ -751,7 +810,11 @@ class NoteStore:
751
810
  self._insert_link(note.parent_id, note.id, note.prev_id, next_id)
752
811
  tag_updates = {}
753
812
  if record.parent_id != note.parent_id:
754
- tag_updates = self._recompute_effective_tag_terms_locked({note.id})
813
+ former_parents = set()
814
+ if record.parent_id is not None:
815
+ former_parents.add(record.parent_id)
816
+ tag_updates = self._recompute_effective_tag_terms_locked(
817
+ {note.id}, subtree_root_ids=former_parents)
755
818
  self._publish_tag_updates(tag_updates)
756
819
 
757
820
  def bulk_update_metadata(self, notes: Iterable[SimpleNamespace], *, rebuild: bool) -> None:
@@ -768,6 +831,7 @@ class NoteStore:
768
831
  self._revision += 1
769
832
  replacements = {}
770
833
  moved_ids: Set[str] = set()
834
+ former_parents: Set[str] = set()
771
835
 
772
836
  for note in payload:
773
837
  record = self._note_map[note.id]
@@ -792,6 +856,8 @@ class NoteStore:
792
856
  replacements[note.id] = updated
793
857
  if record.parent_id != updated.parent_id:
794
858
  moved_ids.add(note.id)
859
+ if record.parent_id is not None:
860
+ former_parents.add(record.parent_id)
795
861
  next_records = dict(self._note_map)
796
862
  next_records.update(replacements)
797
863
  if moved_ids:
@@ -801,7 +867,8 @@ class NoteStore:
801
867
  self._ordering = next_ordering
802
868
 
803
869
  if moved_ids:
804
- tag_updates = self._recompute_effective_tag_terms_locked(moved_ids)
870
+ tag_updates = self._recompute_effective_tag_terms_locked(
871
+ moved_ids, subtree_root_ids=former_parents)
805
872
 
806
873
  self._publish_tag_updates(tag_updates)
807
874
 
@@ -833,9 +900,14 @@ class NoteStore:
833
900
  self._backlink_index.remove(removed_id)
834
901
  self._effective_non_meta_tag_terms.pop(removed_id, None)
835
902
  self._effective_proposed_non_meta_tag_terms.pop(removed_id, None)
903
+ self._subtree_non_meta_tag_terms.pop(removed_id, None)
904
+ self._subtree_proposed_non_meta_tag_terms.pop(removed_id, None)
836
905
 
906
+ former_parents = set()
907
+ if root.parent_id is not None:
908
+ former_parents.add(root.parent_id)
837
909
  tag_updates = self._recompute_effective_tag_terms_locked(
838
- referrer_ids & self._note_map.keys())
910
+ referrer_ids & self._note_map.keys(), subtree_root_ids=former_parents)
839
911
 
840
912
  if removed_ids:
841
913
  search_index.remove_many(removed_ids)
@@ -939,8 +1011,8 @@ class NoteStore:
939
1011
  if record is None:
940
1012
  raise KeyError(f"Note {note_id} not present in NoteStore")
941
1013
 
942
- return frozenset().union(*(self._effective_non_meta_tag_terms[source_id]
943
- for source_id in self._tag_dependencies_locked(note_id)))
1014
+ return self._inherited_tag_terms_locked(
1015
+ note_id, self._effective_non_meta_tag_terms, self._subtree_non_meta_tag_terms)
944
1016
 
945
1017
  def get_inherited_proposed_non_meta_tag_terms(self, note_id: str) -> FrozenSet[str]:
946
1018
  if not isinstance(note_id, str) or not note_id:
@@ -950,8 +1022,8 @@ class NoteStore:
950
1022
  raise RuntimeError("NoteStore is not loaded")
951
1023
  if note_id not in self._note_map:
952
1024
  raise KeyError(f"Note {note_id} not present in NoteStore")
953
- return frozenset().union(*(self._effective_proposed_non_meta_tag_terms[source_id]
954
- for source_id in self._tag_dependencies_locked(note_id)))
1025
+ return self._inherited_tag_terms_locked(
1026
+ note_id, self._effective_proposed_non_meta_tag_terms, self._subtree_proposed_non_meta_tag_terms)
955
1027
 
956
1028
  def apply_bulk_tag_sources(self, changes: Mapping[str, tuple[str, str]]) -> None:
957
1029
  """Publish sources together and rebuild inheritance once for the whole pass."""
@@ -5565,6 +5565,11 @@ body.pref-show-ai-chat .chat-toggle-button {
5565
5565
  }
5566
5566
 
5567
5567
  /* Loading state styling */
5568
+ #version-info-modal:has([data-update-pending="true"]),
5569
+ #version-info-modal:has([data-update-pending="true"]) * {
5570
+ cursor: wait !important;
5571
+ }
5572
+
5568
5573
  body.loading {
5569
5574
  cursor: wait !important;
5570
5575
  }
@@ -124,6 +124,10 @@ export class VersionInfoModal extends BaseModal {
124
124
  const loading = state.loading === true;
125
125
  const error = typeof state.error === 'string' ? state.error : '';
126
126
  const info = state.info;
127
+ let isUpdating = state.updatePending;
128
+ if (state.updateJob !== null && !['complete', 'failed'].includes(state.updateJob.status)) {
129
+ isUpdating = true;
130
+ }
127
131
 
128
132
  let bodyHtml = '<p class="version-info-status">Loading version info...</p>';
129
133
  if (!loading && info !== null) {
@@ -137,7 +141,7 @@ export class VersionInfoModal extends BaseModal {
137
141
  <div class="modal-content version-info-modal-content">
138
142
  <h3>Version Info</h3>
139
143
  ${bodyHtml}
140
- <section class="version-update-section" aria-live="polite">
144
+ <section class="version-update-section" aria-live="polite" data-update-pending="${isUpdating}">
141
145
  <p>${escapeHtml(state.updateMessage)}</p>
142
146
  ${this.buildUpdateControls(state)}
143
147
  </section>