MetaList 0.3.12__tar.gz → 0.3.14__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 (382) hide show
  1. {metalist-0.3.12/metalist.egg-info → metalist-0.3.14}/PKG-INFO +29 -2
  2. {metalist-0.3.12 → metalist-0.3.14}/README.md +27 -1
  3. {metalist-0.3.12 → metalist-0.3.14}/app/api/middleware/auth.py +0 -4
  4. {metalist-0.3.12 → metalist-0.3.14}/app/api/routes/auth.py +47 -198
  5. {metalist-0.3.12 → metalist-0.3.14}/app/api/routes/notes.py +109 -44
  6. {metalist-0.3.12 → metalist-0.3.14}/app/api/routes/ontology.py +13 -4
  7. {metalist-0.3.12 → metalist-0.3.14}/app/config.py +1 -1
  8. {metalist-0.3.12 → metalist-0.3.14}/app/db/file_session.py +2 -0
  9. {metalist-0.3.12 → metalist-0.3.14}/app/db/migrations.py +99 -0
  10. {metalist-0.3.12 → metalist-0.3.14}/app/db/schema.py +4 -20
  11. metalist-0.3.14/app/db/search_history_sql.py +77 -0
  12. {metalist-0.3.12 → metalist-0.3.14}/app/db/version.py +1 -1
  13. {metalist-0.3.12 → metalist-0.3.14}/app/encryption_audit.py +81 -17
  14. {metalist-0.3.12 → metalist-0.3.14}/app/main.py +20 -3
  15. {metalist-0.3.12 → metalist-0.3.14}/app/models/database.py +3 -0
  16. {metalist-0.3.12 → metalist-0.3.14}/app/presentation/render/note_renderer.py +5 -1
  17. metalist-0.3.14/app/presentation/templates.py +13 -0
  18. metalist-0.3.14/app/security/authenticated_logging.py +179 -0
  19. {metalist-0.3.12 → metalist-0.3.14}/app/services/auth_service.py +35 -0
  20. {metalist-0.3.12 → metalist-0.3.14}/app/services/backup_service.py +87 -20
  21. {metalist-0.3.12 → metalist-0.3.14}/app/services/client_state_service.py +27 -0
  22. {metalist-0.3.12 → metalist-0.3.14}/app/services/content_cache.py +14 -6
  23. {metalist-0.3.12 → metalist-0.3.14}/app/services/content_formatting.py +29 -6
  24. {metalist-0.3.12 → metalist-0.3.14}/app/services/diagnostics.py +137 -0
  25. {metalist-0.3.12 → metalist-0.3.14}/app/services/embedded_references.py +86 -87
  26. {metalist-0.3.12 → metalist-0.3.14}/app/services/encryption.py +52 -0
  27. {metalist-0.3.12 → metalist-0.3.14}/app/services/html_export.py +0 -1
  28. {metalist-0.3.12 → metalist-0.3.14}/app/services/html_unformatting.py +73 -22
  29. {metalist-0.3.12 → metalist-0.3.14}/app/services/markdown_rendering.py +7 -0
  30. metalist-0.3.14/app/services/namespace_runtime_guard.py +143 -0
  31. {metalist-0.3.12 → metalist-0.3.14}/app/services/namespace_switcher.py +20 -15
  32. metalist-0.3.14/app/services/note_fullscreen.py +107 -0
  33. {metalist-0.3.12 → metalist-0.3.14}/app/services/note_image_tags.py +8 -21
  34. {metalist-0.3.12 → metalist-0.3.14}/app/services/note_store.py +2 -2
  35. {metalist-0.3.12 → metalist-0.3.14}/app/services/ontology_rules_store.py +41 -0
  36. {metalist-0.3.12 → metalist-0.3.14}/app/services/root_sorting.py +36 -1
  37. metalist-0.3.14/app/services/search_history.py +829 -0
  38. metalist-0.3.14/app/services/search_history_storage.py +153 -0
  39. {metalist-0.3.12 → metalist-0.3.14}/app/services/search_index.py +128 -123
  40. {metalist-0.3.12 → metalist-0.3.14}/app/services/search_query.py +70 -12
  41. {metalist-0.3.12 → metalist-0.3.14}/app/services/selected_text_tag.py +2 -0
  42. {metalist-0.3.12 → metalist-0.3.14}/app/services/self_update.py +91 -8
  43. {metalist-0.3.12 → metalist-0.3.14}/app/services/snapshot.py +224 -117
  44. {metalist-0.3.12 → metalist-0.3.14}/app/services/tag_ontology.py +2 -0
  45. {metalist-0.3.12 → metalist-0.3.14}/app/services/tag_rename.py +77 -0
  46. {metalist-0.3.12 → metalist-0.3.14}/app/services/tag_suggestions.py +1 -1
  47. {metalist-0.3.12 → metalist-0.3.14}/app/services/undo_state.py +2 -277
  48. {metalist-0.3.12 → metalist-0.3.14}/app/services/view_cache.py +19 -5
  49. {metalist-0.3.12 → metalist-0.3.14}/app/services/view_diff.py +3 -0
  50. metalist-0.3.14/app/startup_environment.py +55 -0
  51. {metalist-0.3.12 → metalist-0.3.14}/app/startup_js_sanity.py +22 -0
  52. {metalist-0.3.12 → metalist-0.3.14}/app/startup_sanity.py +253 -2
  53. {metalist-0.3.12 → metalist-0.3.14}/app/static/config/command_palette_tags.json +25 -5
  54. {metalist-0.3.12 → metalist-0.3.14}/app/static/css/main.css +1145 -343
  55. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/main.js +8 -0
  56. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/api-client.js +64 -27
  57. metalist-0.3.14/app/static/js/modules/api-failure-classification-service.js +82 -0
  58. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/auth.js +43 -1
  59. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/command-palette/command-palette-controller.js +246 -16
  60. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/command-palette/endpoint-registry.js +48 -7
  61. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/config.js +4 -2
  62. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/context-menu/context-menu-registry.js +88 -10
  63. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/context-menu/context-menu-service.js +44 -0
  64. metalist-0.3.14/app/static/js/modules/disconnect-editing-state-service.js +30 -0
  65. metalist-0.3.14/app/static/js/modules/elapsed-time.js +18 -0
  66. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/error-handler.js +32 -44
  67. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/error-overlay.js +13 -0
  68. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/backup-restore-modal.js +12 -33
  69. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/backup-result-modal.js +0 -10
  70. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/backup-settings-modal.js +4 -0
  71. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/base-modal.js +58 -3
  72. metalist-0.3.14/app/static/js/modules/modals/confirmation-modal.js +145 -0
  73. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/delete-namespace-modal.js +10 -46
  74. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/delete-namespace-validation.js +0 -15
  75. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/help-modal.js +1 -8
  76. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/ontology-modal.js +122 -40
  77. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/password-modal.js +342 -82
  78. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/random-password-modal.js +18 -36
  79. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/reminder-modal.js +2 -5
  80. metalist-0.3.14/app/static/js/modules/modals/search-suggestion-statistics-modal.js +455 -0
  81. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/session-timeout-modal.js +1 -1
  82. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/sound-manager-modal.js +2 -5
  83. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/version-info-modal.js +0 -9
  84. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/actions/history-actions.js +32 -89
  85. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/actions/note-actions.js +23 -9
  86. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/actions/selection-actions.js +11 -42
  87. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/actions/ui-actions.js +4 -1
  88. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/events/context-menu-events.js +80 -7
  89. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/events/keyboard-events.js +256 -167
  90. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/events/mouse-events.js +26 -125
  91. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/events/search-events.js +36 -8
  92. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/mode-context.js +23 -16
  93. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +24 -5
  94. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -7
  95. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +16 -0
  96. metalist-0.3.14/app/static/js/modules/mode-manager/services/deselect-selection-state-service.js +27 -0
  97. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/differential-view-service.js +35 -2
  98. metalist-0.3.14/app/static/js/modules/mode-manager/services/history-selection-policy-service.js +25 -0
  99. metalist-0.3.14/app/static/js/modules/mode-manager/services/history-shortcut-policy-service.js +23 -0
  100. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +11 -0
  101. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +10 -0
  102. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +41 -3
  103. metalist-0.3.14/app/static/js/modules/mode-manager/services/mermaid-render-service.js +223 -0
  104. metalist-0.3.14/app/static/js/modules/mode-manager/services/note-fullscreen-service.js +95 -0
  105. metalist-0.3.14/app/static/js/modules/mode-manager/services/note-timestamp-hover-service.js +61 -0
  106. metalist-0.3.14/app/static/js/modules/mode-manager/services/reference-source-navigation-service.js +83 -0
  107. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +17 -0
  108. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/root-sort-service.js +5 -0
  109. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/search-input-service.js +13 -0
  110. metalist-0.3.14/app/static/js/modules/mode-manager/services/search-interaction-service.js +125 -0
  111. metalist-0.3.14/app/static/js/modules/mode-manager/services/search-suggestion-statistics-service.js +78 -0
  112. metalist-0.3.14/app/static/js/modules/mode-manager/services/search-suggestion-window-labels-service.js +72 -0
  113. metalist-0.3.14/app/static/js/modules/mode-manager/services/search-suggestion-windows-service.js +80 -0
  114. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +41 -13
  115. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/search-syntax-service.js +54 -0
  116. metalist-0.3.14/app/static/js/modules/mode-manager/services/selected-formatting-removal-service.js +599 -0
  117. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/selected-text-tag-service.js +2 -1
  118. metalist-0.3.14/app/static/js/modules/mode-manager/services/tab-drag-service.js +42 -0
  119. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -2
  120. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +12 -1
  121. metalist-0.3.14/app/static/js/modules/mode-manager/services/untagged-view-indicator-service.js +13 -0
  122. metalist-0.3.14/app/static/js/modules/password-operation-response.js +27 -0
  123. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/password-policy.js +14 -0
  124. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/tag-token.js +3 -0
  125. metalist-0.3.14/app/static/js/vendor/mermaid-11.9.0.LICENSE.txt +21 -0
  126. metalist-0.3.14/app/static/js/vendor/mermaid-11.9.0.min.js +2659 -0
  127. {metalist-0.3.12 → metalist-0.3.14}/app/templates/index.html +60 -29
  128. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/add_selected_text_tag.py +8 -7
  129. metalist-0.3.14/app/usecases/delete_tag.py +66 -0
  130. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/rename_tag.py +2 -0
  131. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/search_comment_autofill.py +4 -3
  132. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/search_context_tags.py +5 -4
  133. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/set_collapse_in_context.py +42 -65
  134. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/split_note.py +0 -7
  135. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/update_content.py +7 -24
  136. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/view.py +1 -0
  137. {metalist-0.3.12 → metalist-0.3.14}/app/version.py +1 -1
  138. {metalist-0.3.12 → metalist-0.3.14}/main.py +12 -0
  139. {metalist-0.3.12 → metalist-0.3.14/metalist.egg-info}/PKG-INFO +29 -2
  140. {metalist-0.3.12 → metalist-0.3.14}/metalist.egg-info/SOURCES.txt +26 -1
  141. {metalist-0.3.12 → metalist-0.3.14}/metalist.egg-info/requires.txt +1 -0
  142. {metalist-0.3.12 → metalist-0.3.14}/pyproject.toml +1 -0
  143. metalist-0.3.12/app/db/search_history_sql.py +0 -213
  144. metalist-0.3.12/app/presentation/templates.py +0 -22
  145. metalist-0.3.12/app/services/search_history.py +0 -987
  146. metalist-0.3.12/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -181
  147. metalist-0.3.12/app/static/js/modules/mode-manager/services/selected-formatting-removal-service.js +0 -207
  148. metalist-0.3.12/app/usecases/record_edit_mode.py +0 -45
  149. {metalist-0.3.12 → metalist-0.3.14}/MANIFEST.in +0 -0
  150. {metalist-0.3.12 → metalist-0.3.14}/app/__init__.py +0 -0
  151. {metalist-0.3.12 → metalist-0.3.14}/app/api/__init__.py +0 -0
  152. {metalist-0.3.12 → metalist-0.3.14}/app/api/deps.py +0 -0
  153. {metalist-0.3.12 → metalist-0.3.14}/app/api/dev.py +0 -0
  154. {metalist-0.3.12 → metalist-0.3.14}/app/api/request_auth.py +0 -0
  155. {metalist-0.3.12 → metalist-0.3.14}/app/api/routes/backups.py +0 -0
  156. {metalist-0.3.12 → metalist-0.3.14}/app/api/routes/files.py +0 -0
  157. {metalist-0.3.12 → metalist-0.3.14}/app/api/routes/reminders.py +0 -0
  158. {metalist-0.3.12 → metalist-0.3.14}/app/api/routes/remote_images.py +0 -0
  159. {metalist-0.3.12 → metalist-0.3.14}/app/api/routes/sounds.py +0 -0
  160. {metalist-0.3.12 → metalist-0.3.14}/app/api/routes/test.py +0 -0
  161. {metalist-0.3.12 → metalist-0.3.14}/app/api/transactions.py +0 -0
  162. {metalist-0.3.12 → metalist-0.3.14}/app/db/__init__.py +0 -0
  163. {metalist-0.3.12 → metalist-0.3.14}/app/db/backup_settings_sql.py +0 -0
  164. {metalist-0.3.12 → metalist-0.3.14}/app/db/engine.py +0 -0
  165. {metalist-0.3.12 → metalist-0.3.14}/app/db/file_schema.py +0 -0
  166. {metalist-0.3.12 → metalist-0.3.14}/app/db/files_sql.py +0 -0
  167. {metalist-0.3.12 → metalist-0.3.14}/app/db/link_titles_sql.py +0 -0
  168. {metalist-0.3.12 → metalist-0.3.14}/app/db/notes_sql.py +0 -0
  169. {metalist-0.3.12 → metalist-0.3.14}/app/db/ontology_rules_sql.py +0 -0
  170. {metalist-0.3.12 → metalist-0.3.14}/app/db/reminders_sql.py +0 -0
  171. {metalist-0.3.12 → metalist-0.3.14}/app/db/session.py +0 -0
  172. {metalist-0.3.12 → metalist-0.3.14}/app/db/settings_sql.py +0 -0
  173. {metalist-0.3.12 → metalist-0.3.14}/app/db/sounds_sql.py +0 -0
  174. {metalist-0.3.12 → metalist-0.3.14}/app/db/tab_state_sql.py +0 -0
  175. {metalist-0.3.12 → metalist-0.3.14}/app/models/commands.py +0 -0
  176. {metalist-0.3.12 → metalist-0.3.14}/app/models/entities.py +0 -0
  177. {metalist-0.3.12 → metalist-0.3.14}/app/models/enums.py +0 -0
  178. {metalist-0.3.12 → metalist-0.3.14}/app/models/linked_list.py +0 -0
  179. {metalist-0.3.12 → metalist-0.3.14}/app/models/list_operations.py +0 -0
  180. {metalist-0.3.12 → metalist-0.3.14}/app/models/list_traversal.py +0 -0
  181. {metalist-0.3.12 → metalist-0.3.14}/app/models/note_crud.py +0 -0
  182. {metalist-0.3.12 → metalist-0.3.14}/app/models/utils.py +0 -0
  183. {metalist-0.3.12 → metalist-0.3.14}/app/presentation/__init__.py +0 -0
  184. {metalist-0.3.12 → metalist-0.3.14}/app/presentation/render/__init__.py +0 -0
  185. {metalist-0.3.12 → metalist-0.3.14}/app/security/encryption.py +0 -0
  186. {metalist-0.3.12 → metalist-0.3.14}/app/security/http_headers.py +0 -0
  187. {metalist-0.3.12 → metalist-0.3.14}/app/security/note_html.py +0 -0
  188. {metalist-0.3.12 → metalist-0.3.14}/app/security/request_boundary.py +0 -0
  189. {metalist-0.3.12 → metalist-0.3.14}/app/security/sensitive_logging.py +0 -0
  190. {metalist-0.3.12 → metalist-0.3.14}/app/security/shell_execution.py +0 -0
  191. {metalist-0.3.12 → metalist-0.3.14}/app/security/validation_errors.py +0 -0
  192. {metalist-0.3.12 → metalist-0.3.14}/app/server_runtime.py +0 -0
  193. {metalist-0.3.12 → metalist-0.3.14}/app/services/__init__.py +0 -0
  194. {metalist-0.3.12 → metalist-0.3.14}/app/services/auth.py +0 -0
  195. {metalist-0.3.12 → metalist-0.3.14}/app/services/auth_cache_state.py +0 -0
  196. {metalist-0.3.12 → metalist-0.3.14}/app/services/backlinks.py +0 -0
  197. {metalist-0.3.12 → metalist-0.3.14}/app/services/backup_settings_service.py +0 -0
  198. {metalist-0.3.12 → metalist-0.3.14}/app/services/base_service.py +0 -0
  199. {metalist-0.3.12 → metalist-0.3.14}/app/services/date_filtering.py +0 -0
  200. {metalist-0.3.12 → metalist-0.3.14}/app/services/dependencies.py +0 -0
  201. {metalist-0.3.12 → metalist-0.3.14}/app/services/exception_capture.py +0 -0
  202. {metalist-0.3.12 → metalist-0.3.14}/app/services/file_registry.py +0 -0
  203. {metalist-0.3.12 → metalist-0.3.14}/app/services/file_storage.py +0 -0
  204. {metalist-0.3.12 → metalist-0.3.14}/app/services/hydration_state.py +0 -0
  205. {metalist-0.3.12 → metalist-0.3.14}/app/services/image_size_formatting.py +0 -0
  206. {metalist-0.3.12 → metalist-0.3.14}/app/services/inline_image_occurrences.py +0 -0
  207. {metalist-0.3.12 → metalist-0.3.14}/app/services/integrity.py +0 -0
  208. {metalist-0.3.12 → metalist-0.3.14}/app/services/latex_rendering.py +0 -0
  209. {metalist-0.3.12 → metalist-0.3.14}/app/services/link_titles.py +0 -0
  210. {metalist-0.3.12 → metalist-0.3.14}/app/services/login_rate_limit.py +0 -0
  211. {metalist-0.3.12 → metalist-0.3.14}/app/services/maintenance_mode.py +0 -0
  212. {metalist-0.3.12 → metalist-0.3.14}/app/services/namespace_deletion_jobs.py +0 -0
  213. {metalist-0.3.12 → metalist-0.3.14}/app/services/namespace_deletion_worker.py +0 -0
  214. {metalist-0.3.12 → metalist-0.3.14}/app/services/namespace_rename_jobs.py +0 -0
  215. {metalist-0.3.12 → metalist-0.3.14}/app/services/namespace_rename_worker.py +0 -0
  216. {metalist-0.3.12 → metalist-0.3.14}/app/services/note_service.py +0 -0
  217. {metalist-0.3.12 → metalist-0.3.14}/app/services/query_service.py +0 -0
  218. {metalist-0.3.12 → metalist-0.3.14}/app/services/reminders.py +0 -0
  219. {metalist-0.3.12 → metalist-0.3.14}/app/services/remote_image_proxy.py +0 -0
  220. {metalist-0.3.12 → metalist-0.3.14}/app/services/runtime_hardening.py +0 -0
  221. {metalist-0.3.12 → metalist-0.3.14}/app/services/runtime_lock.py +0 -0
  222. {metalist-0.3.12 → metalist-0.3.14}/app/services/search_text.py +0 -0
  223. {metalist-0.3.12 → metalist-0.3.14}/app/services/session_timeout_service.py +0 -0
  224. {metalist-0.3.12 → metalist-0.3.14}/app/services/shell_session_service.py +0 -0
  225. {metalist-0.3.12 → metalist-0.3.14}/app/services/sound_storage.py +0 -0
  226. {metalist-0.3.12 → metalist-0.3.14}/app/services/store.py +0 -0
  227. {metalist-0.3.12 → metalist-0.3.14}/app/services/sync.py +0 -0
  228. {metalist-0.3.12 → metalist-0.3.14}/app/services/sync_state.py +0 -0
  229. {metalist-0.3.12 → metalist-0.3.14}/app/services/tab_state.py +0 -0
  230. {metalist-0.3.12 → metalist-0.3.14}/app/services/tag_case.py +0 -0
  231. {metalist-0.3.12 → metalist-0.3.14}/app/services/tag_term_matching.py +0 -0
  232. {metalist-0.3.12 → metalist-0.3.14}/app/services/test_reset.py +0 -0
  233. {metalist-0.3.12 → metalist-0.3.14}/app/services/tokens.py +0 -0
  234. {metalist-0.3.12 → metalist-0.3.14}/app/services/transaction_manager.py +0 -0
  235. {metalist-0.3.12 → metalist-0.3.14}/app/services/undo_service.py +0 -0
  236. {metalist-0.3.12 → metalist-0.3.14}/app/services/view_state.py +0 -0
  237. {metalist-0.3.12 → metalist-0.3.14}/app/services/windows_process_control.py +0 -0
  238. {metalist-0.3.12 → metalist-0.3.14}/app/startup_sanity_config.py +0 -0
  239. {metalist-0.3.12 → metalist-0.3.14}/app/static/css/forms.css +0 -0
  240. {metalist-0.3.12 → metalist-0.3.14}/app/static/img/favicon.ico +0 -0
  241. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/locked.js +0 -0
  242. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/activity-tracker.js +0 -0
  243. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/async-result.js +0 -0
  244. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/client-state-api.js +0 -0
  245. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/client-state-migration.js +0 -0
  246. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/command-palette/activation-auth-policy.js +0 -0
  247. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/command-palette/backup-command-availability.js +0 -0
  248. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
  249. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/command-palette/note-layout-preferences.js +0 -0
  250. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/command-palette/preferences-store.js +0 -0
  251. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/command-palette/tag-config-loader.js +0 -0
  252. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/command-palette/usage-store.js +0 -0
  253. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/connectivity-monitor.js +0 -0
  254. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/context-menu/context-menu-target-service.js +0 -0
  255. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
  256. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/dom-utils-fixed.js +0 -0
  257. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/dom-utils.js +0 -0
  258. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/editor-commands.js +0 -0
  259. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/editor-selection.js +0 -0
  260. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/editor-toolbar.js +0 -0
  261. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/location-flags.js +0 -0
  262. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/login-namespace-picker.js +0 -0
  263. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
  264. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
  265. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/namespace-loading-page.js +0 -0
  266. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/namespace-modals.js +0 -0
  267. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/namespace-ports-profile.js +0 -0
  268. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/namespace-rename-validation.js +0 -0
  269. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/note-layout-appearance-modal.js +0 -0
  270. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/prioritize-modal.js +0 -0
  271. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
  272. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/actions/content-actions.js +0 -0
  273. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
  274. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/events/focus-events.js +0 -0
  275. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
  276. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
  277. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/mode-logger.js +0 -0
  278. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
  279. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/add-style-service.js +0 -0
  280. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
  281. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
  282. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
  283. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
  284. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/date-filter-indicator-service.js +0 -0
  285. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/date-filter-service.js +0 -0
  286. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
  287. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
  288. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
  289. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/file-drop-policy-service.js +0 -0
  290. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
  291. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
  292. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
  293. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +0 -0
  294. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
  295. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
  296. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
  297. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
  298. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
  299. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
  300. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/note-delete-shortcut-policy-service.js +0 -0
  301. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
  302. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
  303. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
  304. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
  305. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
  306. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/remote-image-proxy-service.js +0 -0
  307. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/remove-formatting-service.js +0 -0
  308. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/rhs-panel-service.js +0 -0
  309. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
  310. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
  311. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
  312. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
  313. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
  314. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
  315. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
  316. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
  317. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
  318. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
  319. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/tag-bar-service.js +0 -0
  320. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +0 -0
  321. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
  322. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
  323. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/note-html-sanitizer.js +0 -0
  324. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/password-generator.js +0 -0
  325. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/password-strength.js +0 -0
  326. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/reminder-store.js +0 -0
  327. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/reminder-surface-service.js +0 -0
  328. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/selection-markers.js +0 -0
  329. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/session-auth.js +0 -0
  330. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/sound-service.js +0 -0
  331. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/modules/uuid.js +0 -0
  332. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/namespace-deleted.js +0 -0
  333. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/namespace-renamed.js +0 -0
  334. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/vendor/dompurify-3.4.12.LICENSE.txt +0 -0
  335. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/vendor/dompurify-3.4.12.min.js +0 -0
  336. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/vendor/markdown-it.min.js +0 -0
  337. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/vendor/zxcvbn-4.4.2.LICENSE.txt +0 -0
  338. {metalist-0.3.12 → metalist-0.3.14}/app/static/js/vendor/zxcvbn-4.4.2.js +0 -0
  339. {metalist-0.3.12 → metalist-0.3.14}/app/static/note-html-policy.json +0 -0
  340. {metalist-0.3.12 → metalist-0.3.14}/app/static/video/login-startup.mp4 +0 -0
  341. {metalist-0.3.12 → metalist-0.3.14}/app/templates/base.html +0 -0
  342. {metalist-0.3.12 → metalist-0.3.14}/app/templates/locked.html +0 -0
  343. {metalist-0.3.12 → metalist-0.3.14}/app/templates/maintenance.html +0 -0
  344. {metalist-0.3.12 → metalist-0.3.14}/app/templates/namespace_deleted.html +0 -0
  345. {metalist-0.3.12 → metalist-0.3.14}/app/templates/namespace_renamed.html +0 -0
  346. {metalist-0.3.12 → metalist-0.3.14}/app/templates/notes_list.html +0 -0
  347. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/__init__.py +0 -0
  348. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/alphabetize_root_notes.py +0 -0
  349. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/base.py +0 -0
  350. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/collapse.py +0 -0
  351. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/copy_note.py +0 -0
  352. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/create_child.py +0 -0
  353. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/create_note.py +0 -0
  354. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/create_sibling.py +0 -0
  355. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/delete_subtree.py +0 -0
  356. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/expand.py +0 -0
  357. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/indent.py +0 -0
  358. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/lock.py +0 -0
  359. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/move.py +0 -0
  360. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/move_to_top.py +0 -0
  361. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/outdent.py +0 -0
  362. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/paste_child.py +0 -0
  363. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/paste_sibling.py +0 -0
  364. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/prioritize.py +0 -0
  365. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/redo.py +0 -0
  366. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/reset_updated_at.py +0 -0
  367. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/resize_image.py +0 -0
  368. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/run_shell.py +0 -0
  369. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/set_collapse_bulk.py +0 -0
  370. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/toggle_reference_mode.py +0 -0
  371. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/toggle_todo_done.py +0 -0
  372. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/undo.py +0 -0
  373. {metalist-0.3.12 → metalist-0.3.14}/app/usecases/unformat_content.py +0 -0
  374. {metalist-0.3.12 → metalist-0.3.14}/app/utils/__init__.py +0 -0
  375. {metalist-0.3.12 → metalist-0.3.14}/app/utils/encryption.py +0 -0
  376. {metalist-0.3.12 → metalist-0.3.14}/app/utils/text_utils.py +0 -0
  377. {metalist-0.3.12 → metalist-0.3.14}/convert-from-legacy.py +0 -0
  378. {metalist-0.3.12 → metalist-0.3.14}/metalist.egg-info/dependency_links.txt +0 -0
  379. {metalist-0.3.12 → metalist-0.3.14}/metalist.egg-info/entry_points.txt +0 -0
  380. {metalist-0.3.12 → metalist-0.3.14}/metalist.egg-info/top_level.txt +0 -0
  381. {metalist-0.3.12 → metalist-0.3.14}/scripts/generate-lan-cert.sh +0 -0
  382. {metalist-0.3.12 → metalist-0.3.14}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metalist
3
- Version: 0.3.12
3
+ Version: 0.3.14
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
@@ -19,6 +19,7 @@ Requires-Dist: mutagen==1.48.1
19
19
  Requires-Dist: nh3==0.3.6
20
20
  Requires-Dist: pillow==12.3.0
21
21
  Requires-Dist: pydantic==2.13.4
22
+ Requires-Dist: python-dotenv==1.2.2
22
23
  Requires-Dist: python-multipart==0.0.32
23
24
  Requires-Dist: starlette==1.3.1
24
25
  Requires-Dist: tqdm==4.70.0
@@ -66,6 +67,20 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
66
67
  - The browser client drives interaction via `/api2` JSON endpoints.
67
68
  - Notes are loaded/decrypted into an in-memory store at startup; a post-startup DB read guard prevents accidental runtime SELECTs.
68
69
 
70
+ ## Security Boundary
71
+
72
+ Password protection encrypts namespace data at rest. While a namespace is
73
+ unlocked, the server must hold its data-encryption key and decrypted working
74
+ data in process memory, and the browser holds the decrypted content currently
75
+ rendered in the page. A sufficiently privileged local process, debugger,
76
+ administrator, browser extension, or process-memory dump can therefore expose
77
+ an unlocked namespace.
78
+
79
+ Explicit logout removes the live key and purges decrypted runtime stores, but
80
+ it cannot guarantee forensic overwriting of memory previously allocated by the
81
+ Python or browser runtimes. Protect the host account and operating system, and
82
+ see the detailed [security architecture and threat model](docs/security/README.md).
83
+
69
84
  ## Development
70
85
 
71
86
  ### Setup
@@ -84,7 +99,7 @@ After the first installation, update and restart MetaList with one cross-platfor
84
99
  ```bash
85
100
  metalist update
86
101
  ```
87
- The updater stops running namespaces, hands off to an external PowerShell process on Windows or `/bin/sh` on macOS/Linux so the installed environment can unlock, runs the forced cache-refreshed uv update, and launches MetaList again.
102
+ The updater checks the installed version against the latest PyPI release first. If MetaList is already current, it reports the installed version and leaves all running namespaces untouched. When an update is available, it stops running namespaces, hands off to an external PowerShell process on Windows or `/bin/sh` on macOS/Linux so the installed environment can unlock, installs the exact version reported by PyPI with a forced cache refresh, launches MetaList again, and reports the installed version (for example, `MetaList updated to v0.3.14.`).
88
103
 
89
104
  For pip, users can run `pip install metalist`. For a non-editable local install from this checkout, use `uv pip install .` or `pip install .` instead of the editable command below.
90
105
 
@@ -268,6 +283,18 @@ node --test tests/unit/*.mjs
268
283
  .venv/bin/python -c "from pathlib import Path; import main; main._run_startup_sanity_gates(repo_root=Path.cwd())"
269
284
  ```
270
285
 
286
+ Source-checkout development launches can enable the mandatory Python and
287
+ JavaScript sanity gates with an ignored repository-root `.env` file:
288
+
289
+ ```dotenv
290
+ METALIST_ENVIRONMENT=development
291
+ ```
292
+
293
+ `main.py` prints the resolved environment at startup. An explicit process
294
+ environment value takes precedence over `.env`; only `development` and
295
+ `production` are accepted. When the variable and `.env` file are absent,
296
+ MetaList runs in production mode and skips the source sanity gates.
297
+
271
298
  `TEST_MODE=1` and `POST /api2/test/reset` still exist for deterministic browser automation if we decide to add a new harness later, but Cypress is not part of the current workflow.
272
299
 
273
300
  ### Diagrams
@@ -33,6 +33,20 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
33
33
  - The browser client drives interaction via `/api2` JSON endpoints.
34
34
  - Notes are loaded/decrypted into an in-memory store at startup; a post-startup DB read guard prevents accidental runtime SELECTs.
35
35
 
36
+ ## Security Boundary
37
+
38
+ Password protection encrypts namespace data at rest. While a namespace is
39
+ unlocked, the server must hold its data-encryption key and decrypted working
40
+ data in process memory, and the browser holds the decrypted content currently
41
+ rendered in the page. A sufficiently privileged local process, debugger,
42
+ administrator, browser extension, or process-memory dump can therefore expose
43
+ an unlocked namespace.
44
+
45
+ Explicit logout removes the live key and purges decrypted runtime stores, but
46
+ it cannot guarantee forensic overwriting of memory previously allocated by the
47
+ Python or browser runtimes. Protect the host account and operating system, and
48
+ see the detailed [security architecture and threat model](docs/security/README.md).
49
+
36
50
  ## Development
37
51
 
38
52
  ### Setup
@@ -51,7 +65,7 @@ After the first installation, update and restart MetaList with one cross-platfor
51
65
  ```bash
52
66
  metalist update
53
67
  ```
54
- The updater stops running namespaces, hands off to an external PowerShell process on Windows or `/bin/sh` on macOS/Linux so the installed environment can unlock, runs the forced cache-refreshed uv update, and launches MetaList again.
68
+ The updater checks the installed version against the latest PyPI release first. If MetaList is already current, it reports the installed version and leaves all running namespaces untouched. When an update is available, it stops running namespaces, hands off to an external PowerShell process on Windows or `/bin/sh` on macOS/Linux so the installed environment can unlock, installs the exact version reported by PyPI with a forced cache refresh, launches MetaList again, and reports the installed version (for example, `MetaList updated to v0.3.14.`).
55
69
 
56
70
  For pip, users can run `pip install metalist`. For a non-editable local install from this checkout, use `uv pip install .` or `pip install .` instead of the editable command below.
57
71
 
@@ -235,6 +249,18 @@ node --test tests/unit/*.mjs
235
249
  .venv/bin/python -c "from pathlib import Path; import main; main._run_startup_sanity_gates(repo_root=Path.cwd())"
236
250
  ```
237
251
 
252
+ Source-checkout development launches can enable the mandatory Python and
253
+ JavaScript sanity gates with an ignored repository-root `.env` file:
254
+
255
+ ```dotenv
256
+ METALIST_ENVIRONMENT=development
257
+ ```
258
+
259
+ `main.py` prints the resolved environment at startup. An explicit process
260
+ environment value takes precedence over `.env`; only `development` and
261
+ `production` are accepted. When the variable and `.env` file are absent,
262
+ MetaList runs in production mode and skips the source sanity gates.
263
+
238
264
  `TEST_MODE=1` and `POST /api2/test/reset` still exist for deterministic browser automation if we decide to add a new harness later, but Cypress is not part of the current workflow.
239
265
 
240
266
  ### Diagrams
@@ -7,7 +7,6 @@ from app.api.request_auth import read_request_auth_token
7
7
  from app.services.tokens import token_service
8
8
  from app.services.auth_service import AuthService
9
9
  from app.services.maintenance_mode import maintenance_service
10
- from app.services.runtime_lock import purge_decrypted_runtime_state
11
10
  from app.models.database import SafeSession
12
11
  from app.config import API_PREFIX, TEST_MODE
13
12
 
@@ -62,9 +61,6 @@ class AuthMiddleware(BaseHTTPMiddleware):
62
61
  """Check authentication for protected routes."""
63
62
  path = request.url.path
64
63
 
65
- if not token_service.has_active_tokens():
66
- purge_decrypted_runtime_state()
67
-
68
64
  # Block any v1 API usage with an explicit 410 Gone (no DB access)
69
65
  if path.startswith('/api') and not (path.startswith(API_PREFIX) or path == API_PREFIX):
70
66
  ref = request.headers.get('referer', '-')
@@ -1,10 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- import secrets
5
- import sqlite3
6
4
  from fastapi import APIRouter, Depends, Header, HTTPException, Request, Response
7
- from pydantic import BaseModel, Field
5
+ from pydantic import BaseModel, Field, SecretStr
8
6
  from typing import Annotated, Optional
9
7
  from concurrent.futures import Future, ThreadPoolExecutor
10
8
  from threading import Lock
@@ -16,20 +14,13 @@ import time
16
14
  from app.api.transactions import transactional_route
17
15
  from app.api.deps import get_db
18
16
  from app.config import ACTIVE_NAMESPACE
19
- from app.config import KDF_ALGORITHM
20
- from app.config import KDF_MAX_MEMORY_COST_KIB
21
- from app.config import KDF_MAX_PARALLELISM
22
- from app.config import KDF_MAX_TIME_COST
23
- from app.config import KDF_MIN_MEMORY_COST_KIB
24
- from app.config import KDF_MIN_PARALLELISM
25
- from app.config import KDF_MIN_TIME_COST
26
17
  from app.config import KDF_TIME_COST
27
- from app.config import VAULT_VERSION
28
18
  from app.config import VERSION
29
- from app.db.schema import APP_SETTINGS_TABLE
19
+ from app.db.migrations import CURRENT_DATABASE_VERSION
30
20
  from app.db.settings_sql import fetch_settings
31
21
  from app.models.database import SafeSession
32
22
  from app.services.auth_service import AuthService
23
+ from app.services.auth_service import migrate_restored_database_if_unlocked
33
24
  from app.services.backup_service import (
34
25
  BackupFileInfo,
35
26
  create_timestamped_backup,
@@ -70,6 +61,8 @@ from app.services.reminders import reminder_store
70
61
  from app.services.search_history import search_history_store
71
62
  from app.services.sound_storage import sound_store
72
63
  from app.services.runtime_lock import purge_decrypted_runtime_state
64
+ from app.services.diagnostics import activate_authenticated_logging
65
+ from app.services.diagnostics import deactivate_authenticated_logging
73
66
  from app.services.hydration_state import hydration_state
74
67
  from app.services.file_registry import file_registry
75
68
  from app.services.file_storage import bootstrap_file_registry
@@ -98,7 +91,7 @@ logger = logging.getLogger(__name__)
98
91
 
99
92
 
100
93
  class LoginRequest(BaseModel):
101
- password: str
94
+ password: SecretStr
102
95
 
103
96
 
104
97
  class LoginResponse(BaseModel):
@@ -143,17 +136,17 @@ class HydrationStatusResponse(BaseModel):
143
136
 
144
137
 
145
138
  class PasswordCreateRequest(BaseModel):
146
- password: str
139
+ password: SecretStr
147
140
 
148
141
 
149
142
  class PasswordChangeRequest(BaseModel):
150
- current_password: str
151
- new_password: str
143
+ current_password: SecretStr
144
+ new_password: SecretStr
152
145
  iterations: Optional[int] = None
153
146
 
154
147
 
155
148
  class PasswordRemoveRequest(BaseModel):
156
- current_password: str
149
+ current_password: SecretStr
157
150
 
158
151
 
159
152
  class SessionTimeoutResponse(BaseModel):
@@ -226,6 +219,14 @@ _server_restart_lock = Lock()
226
219
  _server_restart_scheduled = False
227
220
 
228
221
 
222
+ def _active_diagnostics_namespace() -> str:
223
+ if ACTIVE_NAMESPACE is None:
224
+ return "default"
225
+ if ACTIVE_NAMESPACE == "":
226
+ raise RuntimeError("Active namespace must not be empty")
227
+ return ACTIVE_NAMESPACE
228
+
229
+
229
230
  def _reexec_server_process() -> None:
230
231
  argv = [sys.executable, *sys.argv]
231
232
  os.execv(sys.executable, argv)
@@ -265,156 +266,6 @@ def _namespace_target_exists(*, namespace: str) -> bool:
265
266
  return False
266
267
 
267
268
 
268
- def _read_namespace_auth_settings(*, namespace: str) -> dict[str, object] | None:
269
- database_path = resolve_namespaced_database_path(namespace=namespace)
270
- if not database_path.exists():
271
- return None
272
- if not database_path.is_file():
273
- raise RuntimeError(f"Namespace database path is not a file: {database_path}")
274
-
275
- connection = sqlite3.connect(database_path)
276
- connection.row_factory = sqlite3.Row
277
- try:
278
- table_row = connection.execute(
279
- "SELECT 1 FROM sqlite_master WHERE type = ? AND name = ?",
280
- ("table", APP_SETTINGS_TABLE),
281
- ).fetchone()
282
- if table_row is None:
283
- raise RuntimeError(f"Namespace database has no {APP_SETTINGS_TABLE} table: {namespace}")
284
- row = connection.execute(
285
- f"""
286
- SELECT
287
- auth_verifier,
288
- auth_salt,
289
- auth_iterations,
290
- kek_iterations,
291
- vault_version,
292
- kdf_algorithm,
293
- kdf_memory_cost_kib,
294
- kdf_parallelism,
295
- encryption_enabled
296
- FROM {APP_SETTINGS_TABLE}
297
- WHERE id = 1
298
- """
299
- ).fetchone()
300
- if row is None:
301
- raise RuntimeError(f"Namespace database has no app_settings row: {namespace}")
302
- return {
303
- "auth_verifier": row["auth_verifier"],
304
- "auth_salt": row["auth_salt"],
305
- "auth_iterations": row["auth_iterations"],
306
- "kek_iterations": row["kek_iterations"],
307
- "vault_version": row["vault_version"],
308
- "kdf_algorithm": row["kdf_algorithm"],
309
- "kdf_memory_cost_kib": row["kdf_memory_cost_kib"],
310
- "kdf_parallelism": row["kdf_parallelism"],
311
- "encryption_enabled": row["encryption_enabled"],
312
- }
313
- finally:
314
- connection.close()
315
-
316
-
317
- def _namespace_auth_settings_have_password(*, settings: dict[str, object] | None) -> bool:
318
- if settings is None:
319
- return False
320
- encryption_enabled = settings["encryption_enabled"]
321
- if encryption_enabled not in (0, 1, False, True):
322
- raise RuntimeError(f"Invalid encryption_enabled value in namespace DB: {encryption_enabled!r}")
323
- if not bool(encryption_enabled):
324
- return False
325
- _assert_supported_namespace_vault_profile(settings=settings)
326
- if settings["auth_verifier"] is None:
327
- raise RuntimeError("Namespace encryption is enabled but auth_verifier is missing")
328
- if settings["auth_salt"] is None:
329
- raise RuntimeError("Namespace auth_verifier is set but auth_salt is NULL")
330
- if settings["auth_iterations"] is None:
331
- raise RuntimeError("Namespace auth_verifier is set but auth_iterations is NULL")
332
- return True
333
-
334
-
335
- def _namespace_requires_password(*, namespace: str) -> bool:
336
- settings = _read_namespace_auth_settings(namespace=namespace)
337
- return _namespace_auth_settings_have_password(settings=settings)
338
-
339
-
340
- def _assert_supported_namespace_vault_profile(*, settings: dict[str, object]) -> None:
341
- vault_version = settings["vault_version"]
342
- if vault_version is None:
343
- raise RuntimeError("Namespace encryption is enabled but vault_version is NULL")
344
- if vault_version != VAULT_VERSION:
345
- raise RuntimeError(f"Unsupported namespace vault version: {vault_version}")
346
- kdf_algorithm = settings["kdf_algorithm"]
347
- if kdf_algorithm is None:
348
- raise RuntimeError("Namespace encryption is enabled but kdf_algorithm is NULL")
349
- if kdf_algorithm != KDF_ALGORITHM:
350
- raise RuntimeError(f"Unsupported namespace kdf_algorithm: {kdf_algorithm}")
351
- auth_iterations = settings["auth_iterations"]
352
- if auth_iterations is None:
353
- raise RuntimeError("Namespace encryption is enabled but auth_iterations is NULL")
354
- if not isinstance(auth_iterations, int):
355
- raise RuntimeError(f"Namespace auth_iterations is not an integer: {auth_iterations!r}")
356
- if not (KDF_MIN_TIME_COST <= auth_iterations <= KDF_MAX_TIME_COST):
357
- raise RuntimeError(f"Namespace auth_iterations out of range: {auth_iterations}")
358
- kek_iterations = settings["kek_iterations"]
359
- if kek_iterations is None:
360
- raise RuntimeError("Namespace encryption is enabled but kek_iterations is NULL")
361
- if not isinstance(kek_iterations, int):
362
- raise RuntimeError(f"Namespace kek_iterations is not an integer: {kek_iterations!r}")
363
- if not (KDF_MIN_TIME_COST <= kek_iterations <= KDF_MAX_TIME_COST):
364
- raise RuntimeError(f"Namespace kek_iterations out of range: {kek_iterations}")
365
- memory_cost_kib = settings["kdf_memory_cost_kib"]
366
- if memory_cost_kib is None:
367
- raise RuntimeError("Namespace encryption is enabled but kdf_memory_cost_kib is NULL")
368
- if not isinstance(memory_cost_kib, int):
369
- raise RuntimeError(f"Namespace kdf_memory_cost_kib is not an integer: {memory_cost_kib!r}")
370
- if not (KDF_MIN_MEMORY_COST_KIB <= memory_cost_kib <= KDF_MAX_MEMORY_COST_KIB):
371
- raise RuntimeError(f"Namespace kdf_memory_cost_kib out of range: {memory_cost_kib}")
372
- parallelism = settings["kdf_parallelism"]
373
- if parallelism is None:
374
- raise RuntimeError("Namespace encryption is enabled but kdf_parallelism is NULL")
375
- if not isinstance(parallelism, int):
376
- raise RuntimeError(f"Namespace kdf_parallelism is not an integer: {parallelism!r}")
377
- if not (KDF_MIN_PARALLELISM <= parallelism <= KDF_MAX_PARALLELISM):
378
- raise RuntimeError(f"Namespace kdf_parallelism out of range: {parallelism}")
379
-
380
-
381
- def _verify_namespace_password(*, namespace: str, password: str) -> None:
382
- if not isinstance(password, str):
383
- raise TypeError("namespace password must be a string")
384
- settings = _read_namespace_auth_settings(namespace=namespace)
385
- if not _namespace_auth_settings_have_password(settings=settings):
386
- return
387
- if password == "":
388
- raise HTTPException(status_code=400, detail="Namespace password is required")
389
-
390
- assert settings is not None
391
- auth_salt = settings["auth_salt"]
392
- auth_iterations = settings["auth_iterations"]
393
- memory_cost_kib = settings["kdf_memory_cost_kib"]
394
- parallelism = settings["kdf_parallelism"]
395
- auth_verifier = settings["auth_verifier"]
396
- if not isinstance(auth_salt, bytes):
397
- raise RuntimeError("Namespace auth_salt is not bytes")
398
- if not isinstance(auth_iterations, int):
399
- raise RuntimeError("Namespace auth_iterations is not an integer")
400
- if not isinstance(memory_cost_kib, int):
401
- raise RuntimeError("Namespace kdf_memory_cost_kib is not an integer")
402
- if not isinstance(parallelism, int):
403
- raise RuntimeError("Namespace kdf_parallelism is not an integer")
404
- if not isinstance(auth_verifier, str):
405
- raise RuntimeError("Namespace auth_verifier is not a string")
406
-
407
- candidate = EncryptionService().derive_master_key(
408
- password,
409
- auth_salt,
410
- auth_iterations,
411
- memory_cost_kib,
412
- parallelism,
413
- ).hex()
414
- if not secrets.compare_digest(candidate, auth_verifier):
415
- raise HTTPException(status_code=401, detail="Invalid password")
416
-
417
-
418
269
  def _run_hydration(*, rebuild_required: bool) -> None:
419
270
  hydration_state.set_phase(
420
271
  phase="database_check",
@@ -505,19 +356,27 @@ def _reset_runtime_state_after_restore() -> bool:
505
356
 
506
357
  session = SafeSession()
507
358
  try:
359
+ connection = session.connection()
508
360
  with SafeSession.allow_reads("auth:backup_restore:settings"):
509
- settings = fetch_settings(session.connection())
361
+ settings = fetch_settings(connection)
510
362
  if settings is None:
511
363
  raise RuntimeError("App settings missing after backup restore")
512
364
 
513
365
  password_required = bool(settings["encryption_enabled"])
514
366
  set_encryption_required(password_required)
367
+ database_version = migrate_restored_database_if_unlocked(
368
+ connection=connection,
369
+ password_required=password_required,
370
+ )
515
371
 
516
372
  with SafeSession.allow_reads("auth:backup_restore:runtime_stores"):
517
- tab_state_store.bootstrap(connection=session.connection())
518
- link_title_store.bootstrap(connection=session.connection())
519
- reminder_store.bootstrap(connection=session.connection())
520
- search_history_store.bootstrap(connection=session.connection())
373
+ tab_state_store.bootstrap(connection=connection)
374
+ link_title_store.bootstrap(connection=connection)
375
+ reminder_store.bootstrap(connection=connection)
376
+ if not password_required or database_version == CURRENT_DATABASE_VERSION:
377
+ search_history_store.bootstrap(connection=connection)
378
+ else:
379
+ search_history_store.reset()
521
380
 
522
381
  if password_required:
523
382
  return True
@@ -587,15 +446,6 @@ def _require_string_field(payload: dict[str, object], field_name: str) -> str:
587
446
  return value
588
447
 
589
448
 
590
- def _require_string_field_allow_empty(payload: dict[str, object], field_name: str) -> str:
591
- if field_name not in payload:
592
- raise HTTPException(status_code=400, detail=f"{field_name} is required")
593
- value = payload[field_name]
594
- if not isinstance(value, str):
595
- raise HTTPException(status_code=400, detail=f"{field_name} must be a string")
596
- return value
597
-
598
-
599
449
  def _require_int_field(payload: dict[str, object], field_name: str) -> int:
600
450
  if field_name not in payload:
601
451
  raise HTTPException(status_code=400, detail=f"{field_name} is required")
@@ -634,6 +484,7 @@ def login(
634
484
  tab_id: Annotated[str, Depends(_require_tab_id)],
635
485
  db: Annotated[SafeSession, Depends(get_db)],
636
486
  ):
487
+ submitted_password = payload.password.get_secret_value()
637
488
  rate_limit_key = _login_rate_limit_key(request)
638
489
  allowed, retry_after_seconds = login_rate_limiter.check_allowed(rate_limit_key)
639
490
  if not allowed:
@@ -645,14 +496,20 @@ def login(
645
496
  auth = AuthService(db)
646
497
  if not auth.has_password():
647
498
  raise HTTPException(status_code=400, detail="No password is set. Please set a password first.")
648
- if not auth.verify_password(payload.password):
499
+ if not auth.verify_password(submitted_password):
649
500
  login_rate_limiter.record_failure(rate_limit_key)
650
501
  raise HTTPException(status_code=401, detail="Invalid password")
651
502
 
652
- dek = auth.unwrap_dek_for_password(payload.password)
503
+ dek = auth.unwrap_dek_for_password(submitted_password)
653
504
  auth.run_authenticated_database_migrations(dek=dek)
654
505
 
655
506
  set_session_dek(dek)
507
+ with SafeSession.allow_reads("auth:search_history_post_migration"):
508
+ search_history_store.bootstrap(connection=db.connection())
509
+ activate_authenticated_logging(
510
+ namespace=_active_diagnostics_namespace(),
511
+ dek=dek,
512
+ )
656
513
  ensure_rules_decrypted_and_compiled(token="")
657
514
  tab_state_store.ensure_decrypted(token="")
658
515
  link_title_store.ensure_decrypted(token="")
@@ -679,6 +536,7 @@ def logout(
679
536
  if not purge_decrypted_runtime_state():
680
537
  clear_all_locks()
681
538
  clear_encryption_key()
539
+ deactivate_authenticated_logging()
682
540
  clear_auth_cookie(response=response)
683
541
  return {"message": "Logout successful"}
684
542
 
@@ -726,6 +584,7 @@ def restore_from_backup(
726
584
  try:
727
585
  backup_file = restore_backup(payload.filename)
728
586
  password_required = _reset_runtime_state_after_restore()
587
+ deactivate_authenticated_logging()
729
588
  finally:
730
589
  maintenance_service.exit_maintenance()
731
590
 
@@ -978,15 +837,8 @@ def save_namespace_ports(
978
837
  def _delete_namespace_from_body(*, body: dict[str, object], target_namespace: str) -> dict[str, object]:
979
838
  normalized_target_namespace = validate_namespace(namespace=target_namespace)
980
839
  confirmed_namespace = _require_string_field(body, "confirmed_namespace")
981
- current_password = _require_string_field_allow_empty(body, "current_password")
982
840
  redirect_namespace = _require_string_field(body, "redirect_namespace")
983
841
 
984
- if _namespace_requires_password(namespace=normalized_target_namespace):
985
- _verify_namespace_password(
986
- namespace=normalized_target_namespace,
987
- password=current_password,
988
- )
989
-
990
842
  delete_capture = CapturedExceptionContext(
991
843
  RuntimeError,
992
844
  ValueError,
@@ -1028,9 +880,6 @@ def namespace_delete_preflight(
1028
880
  namespace=_require_string_field(body, "target_namespace"),
1029
881
  )
1030
882
  target_exists = _namespace_target_exists(namespace=target_namespace)
1031
- target_requires_password = False
1032
- if target_exists:
1033
- target_requires_password = _namespace_requires_password(namespace=target_namespace)
1034
883
  catalog = build_namespace_catalog(
1035
884
  environ=os.environ,
1036
885
  current_namespace=ACTIVE_NAMESPACE,
@@ -1054,7 +903,6 @@ def namespace_delete_preflight(
1054
903
  return {
1055
904
  "target_namespace": target_namespace,
1056
905
  "target_exists": target_exists,
1057
- "target_requires_password": target_requires_password,
1058
906
  "is_current_namespace": target_namespace == ACTIVE_NAMESPACE,
1059
907
  "redirect_namespaces": redirect_namespaces,
1060
908
  "recreates_default": recreates_default,
@@ -1199,7 +1047,7 @@ def create_password(
1199
1047
  auth = AuthService(db)
1200
1048
  if auth.has_password():
1201
1049
  raise HTTPException(status_code=400, detail="Password already exists. Use change endpoint instead.")
1202
- success, message = auth.set_password(payload.password, KDF_TIME_COST)
1050
+ success, message = auth.set_password(payload.password.get_secret_value(), KDF_TIME_COST)
1203
1051
  if not success:
1204
1052
  raise HTTPException(status_code=400, detail=message)
1205
1053
  token_service.revoke_all_tokens()
@@ -1220,8 +1068,8 @@ def change_password(
1220
1068
  else:
1221
1069
  time_cost = KDF_TIME_COST
1222
1070
  success, message = auth.change_password(
1223
- payload.current_password,
1224
- payload.new_password,
1071
+ payload.current_password.get_secret_value(),
1072
+ payload.new_password.get_secret_value(),
1225
1073
  time_cost,
1226
1074
  )
1227
1075
  if not success:
@@ -1239,12 +1087,13 @@ def remove_password(
1239
1087
  token: Annotated[str, Depends(_require_auth)],
1240
1088
  ):
1241
1089
  auth = AuthService(db)
1242
- success, message = auth.remove_password(payload.current_password)
1090
+ success, message = auth.remove_password(payload.current_password.get_secret_value())
1243
1091
  if not success:
1244
1092
  raise HTTPException(status_code=400, detail=message)
1245
1093
  token_service.revoke_all_tokens()
1246
1094
  clear_all_locks()
1247
1095
  clear_encryption_key()
1096
+ deactivate_authenticated_logging()
1248
1097
  return {"message": message}
1249
1098
 
1250
1099