MetaList 0.3.0__tar.gz → 0.3.2__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 (322) hide show
  1. {metalist-0.3.0/metalist.egg-info → metalist-0.3.2}/PKG-INFO +19 -17
  2. {metalist-0.3.0 → metalist-0.3.2}/README.md +2 -2
  3. {metalist-0.3.0 → metalist-0.3.2}/app/api/routes/auth.py +34 -18
  4. {metalist-0.3.0 → metalist-0.3.2}/app/config.py +2 -1
  5. metalist-0.3.2/app/db/migrations.py +166 -0
  6. {metalist-0.3.0 → metalist-0.3.2}/app/db/schema.py +14 -0
  7. {metalist-0.3.0 → metalist-0.3.2}/app/db/settings_sql.py +56 -0
  8. metalist-0.3.2/app/encryption_audit.py +776 -0
  9. {metalist-0.3.0 → metalist-0.3.2}/app/main.py +18 -0
  10. {metalist-0.3.0 → metalist-0.3.2}/app/services/auth_service.py +49 -0
  11. {metalist-0.3.0 → metalist-0.3.2}/app/services/client_state_service.py +172 -11
  12. {metalist-0.3.0 → metalist-0.3.2}/app/services/hydration_state.py +2 -1
  13. {metalist-0.3.0 → metalist-0.3.2}/app/services/latex_rendering.py +25 -14
  14. {metalist-0.3.0 → metalist-0.3.2}/app/services/markdown_rendering.py +165 -0
  15. {metalist-0.3.0 → metalist-0.3.2}/app/services/namespace_switcher.py +33 -8
  16. {metalist-0.3.0 → metalist-0.3.2}/app/services/note_store.py +17 -2
  17. {metalist-0.3.0 → metalist-0.3.2}/app/services/search_index.py +90 -3
  18. {metalist-0.3.0 → metalist-0.3.2}/app/services/sound_storage.py +1 -1
  19. {metalist-0.3.0 → metalist-0.3.2}/app/services/tag_suggestions.py +235 -15
  20. {metalist-0.3.0 → metalist-0.3.2}/app/services/tag_term_matching.py +44 -5
  21. {metalist-0.3.0 → metalist-0.3.2}/app/startup_js_sanity.py +18 -1
  22. {metalist-0.3.0 → metalist-0.3.2}/app/startup_sanity.py +20 -1
  23. {metalist-0.3.0 → metalist-0.3.2}/app/startup_sanity_config.py +14 -0
  24. {metalist-0.3.0 → metalist-0.3.2}/app/static/css/main.css +12 -6
  25. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/auth.js +66 -39
  26. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/client-state-api.js +13 -3
  27. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/login-namespace-picker.js +13 -0
  28. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/namespace-modals.js +5 -12
  29. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/events/context-menu-events.js +30 -25
  30. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/events/keyboard-events.js +1 -5
  31. metalist-0.3.2/app/static/js/modules/mode-manager/services/context-menu-target-service.js +78 -0
  32. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +45 -9
  33. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +3 -0
  34. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/tag-bar-service.js +1 -1
  35. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +3 -0
  36. metalist-0.3.2/app/version.py +3 -0
  37. {metalist-0.3.0 → metalist-0.3.2}/main.py +34 -0
  38. {metalist-0.3.0 → metalist-0.3.2/metalist.egg-info}/PKG-INFO +19 -17
  39. {metalist-0.3.0 → metalist-0.3.2}/metalist.egg-info/SOURCES.txt +4 -0
  40. {metalist-0.3.0 → metalist-0.3.2}/metalist.egg-info/entry_points.txt +1 -0
  41. metalist-0.3.2/metalist.egg-info/requires.txt +20 -0
  42. {metalist-0.3.0 → metalist-0.3.2}/pyproject.toml +22 -16
  43. metalist-0.3.0/metalist.egg-info/requires.txt +0 -18
  44. {metalist-0.3.0 → metalist-0.3.2}/MANIFEST.in +0 -0
  45. {metalist-0.3.0 → metalist-0.3.2}/app/__init__.py +0 -0
  46. {metalist-0.3.0 → metalist-0.3.2}/app/api/__init__.py +0 -0
  47. {metalist-0.3.0 → metalist-0.3.2}/app/api/deps.py +0 -0
  48. {metalist-0.3.0 → metalist-0.3.2}/app/api/dev.py +0 -0
  49. {metalist-0.3.0 → metalist-0.3.2}/app/api/middleware/auth.py +0 -0
  50. {metalist-0.3.0 → metalist-0.3.2}/app/api/request_auth.py +0 -0
  51. {metalist-0.3.0 → metalist-0.3.2}/app/api/routes/backups.py +0 -0
  52. {metalist-0.3.0 → metalist-0.3.2}/app/api/routes/files.py +0 -0
  53. {metalist-0.3.0 → metalist-0.3.2}/app/api/routes/mcp.py +0 -0
  54. {metalist-0.3.0 → metalist-0.3.2}/app/api/routes/memory.py +0 -0
  55. {metalist-0.3.0 → metalist-0.3.2}/app/api/routes/notes.py +0 -0
  56. {metalist-0.3.0 → metalist-0.3.2}/app/api/routes/ontology.py +0 -0
  57. {metalist-0.3.0 → metalist-0.3.2}/app/api/routes/reminders.py +0 -0
  58. {metalist-0.3.0 → metalist-0.3.2}/app/api/routes/sounds.py +0 -0
  59. {metalist-0.3.0 → metalist-0.3.2}/app/api/routes/test.py +0 -0
  60. {metalist-0.3.0 → metalist-0.3.2}/app/api/transactions.py +0 -0
  61. {metalist-0.3.0 → metalist-0.3.2}/app/db/__init__.py +0 -0
  62. {metalist-0.3.0 → metalist-0.3.2}/app/db/backup_settings_sql.py +0 -0
  63. {metalist-0.3.0 → metalist-0.3.2}/app/db/engine.py +0 -0
  64. {metalist-0.3.0 → metalist-0.3.2}/app/db/file_schema.py +0 -0
  65. {metalist-0.3.0 → metalist-0.3.2}/app/db/file_session.py +0 -0
  66. {metalist-0.3.0 → metalist-0.3.2}/app/db/files_sql.py +0 -0
  67. {metalist-0.3.0 → metalist-0.3.2}/app/db/link_titles_sql.py +0 -0
  68. {metalist-0.3.0 → metalist-0.3.2}/app/db/notes_sql.py +0 -0
  69. {metalist-0.3.0 → metalist-0.3.2}/app/db/ontology_rules_sql.py +0 -0
  70. {metalist-0.3.0 → metalist-0.3.2}/app/db/reminders_sql.py +0 -0
  71. {metalist-0.3.0 → metalist-0.3.2}/app/db/search_history_sql.py +0 -0
  72. {metalist-0.3.0 → metalist-0.3.2}/app/db/session.py +0 -0
  73. {metalist-0.3.0 → metalist-0.3.2}/app/db/sounds_sql.py +0 -0
  74. {metalist-0.3.0 → metalist-0.3.2}/app/db/tab_state_sql.py +0 -0
  75. {metalist-0.3.0 → metalist-0.3.2}/app/mcp/__init__.py +0 -0
  76. {metalist-0.3.0 → metalist-0.3.2}/app/mcp/__main__.py +0 -0
  77. {metalist-0.3.0 → metalist-0.3.2}/app/mcp/errors.py +0 -0
  78. {metalist-0.3.0 → metalist-0.3.2}/app/mcp/policy.py +0 -0
  79. {metalist-0.3.0 → metalist-0.3.2}/app/mcp/read_service.py +0 -0
  80. {metalist-0.3.0 → metalist-0.3.2}/app/mcp/server.py +0 -0
  81. {metalist-0.3.0 → metalist-0.3.2}/app/mcp/tools.py +0 -0
  82. {metalist-0.3.0 → metalist-0.3.2}/app/models/commands.py +0 -0
  83. {metalist-0.3.0 → metalist-0.3.2}/app/models/database.py +0 -0
  84. {metalist-0.3.0 → metalist-0.3.2}/app/models/entities.py +0 -0
  85. {metalist-0.3.0 → metalist-0.3.2}/app/models/enums.py +0 -0
  86. {metalist-0.3.0 → metalist-0.3.2}/app/models/linked_list.py +0 -0
  87. {metalist-0.3.0 → metalist-0.3.2}/app/models/list_operations.py +0 -0
  88. {metalist-0.3.0 → metalist-0.3.2}/app/models/list_traversal.py +0 -0
  89. {metalist-0.3.0 → metalist-0.3.2}/app/models/note_crud.py +0 -0
  90. {metalist-0.3.0 → metalist-0.3.2}/app/models/utils.py +0 -0
  91. {metalist-0.3.0 → metalist-0.3.2}/app/presentation/__init__.py +0 -0
  92. {metalist-0.3.0 → metalist-0.3.2}/app/presentation/render/__init__.py +0 -0
  93. {metalist-0.3.0 → metalist-0.3.2}/app/presentation/render/note_renderer.py +0 -0
  94. {metalist-0.3.0 → metalist-0.3.2}/app/presentation/templates.py +0 -0
  95. {metalist-0.3.0 → metalist-0.3.2}/app/security/encryption.py +0 -0
  96. {metalist-0.3.0 → metalist-0.3.2}/app/server_runtime.py +0 -0
  97. {metalist-0.3.0 → metalist-0.3.2}/app/services/__init__.py +0 -0
  98. {metalist-0.3.0 → metalist-0.3.2}/app/services/auth.py +0 -0
  99. {metalist-0.3.0 → metalist-0.3.2}/app/services/auth_cache_state.py +0 -0
  100. {metalist-0.3.0 → metalist-0.3.2}/app/services/backlinks.py +0 -0
  101. {metalist-0.3.0 → metalist-0.3.2}/app/services/backup_service.py +0 -0
  102. {metalist-0.3.0 → metalist-0.3.2}/app/services/backup_settings_service.py +0 -0
  103. {metalist-0.3.0 → metalist-0.3.2}/app/services/base_service.py +0 -0
  104. {metalist-0.3.0 → metalist-0.3.2}/app/services/content_cache.py +0 -0
  105. {metalist-0.3.0 → metalist-0.3.2}/app/services/content_formatting.py +0 -0
  106. {metalist-0.3.0 → metalist-0.3.2}/app/services/date_filtering.py +0 -0
  107. {metalist-0.3.0 → metalist-0.3.2}/app/services/dependencies.py +0 -0
  108. {metalist-0.3.0 → metalist-0.3.2}/app/services/diagnostics.py +0 -0
  109. {metalist-0.3.0 → metalist-0.3.2}/app/services/embedded_references.py +0 -0
  110. {metalist-0.3.0 → metalist-0.3.2}/app/services/encryption.py +0 -0
  111. {metalist-0.3.0 → metalist-0.3.2}/app/services/exception_capture.py +0 -0
  112. {metalist-0.3.0 → metalist-0.3.2}/app/services/file_registry.py +0 -0
  113. {metalist-0.3.0 → metalist-0.3.2}/app/services/file_storage.py +0 -0
  114. {metalist-0.3.0 → metalist-0.3.2}/app/services/html_export.py +0 -0
  115. {metalist-0.3.0 → metalist-0.3.2}/app/services/html_unformatting.py +0 -0
  116. {metalist-0.3.0 → metalist-0.3.2}/app/services/integrity.py +0 -0
  117. {metalist-0.3.0 → metalist-0.3.2}/app/services/link_titles.py +0 -0
  118. {metalist-0.3.0 → metalist-0.3.2}/app/services/login_rate_limit.py +0 -0
  119. {metalist-0.3.0 → metalist-0.3.2}/app/services/maintenance_mode.py +0 -0
  120. {metalist-0.3.0 → metalist-0.3.2}/app/services/memory.py +0 -0
  121. {metalist-0.3.0 → metalist-0.3.2}/app/services/memory_service.py +0 -0
  122. {metalist-0.3.0 → metalist-0.3.2}/app/services/namespace_deletion_jobs.py +0 -0
  123. {metalist-0.3.0 → metalist-0.3.2}/app/services/namespace_deletion_worker.py +0 -0
  124. {metalist-0.3.0 → metalist-0.3.2}/app/services/note_image_tags.py +0 -0
  125. {metalist-0.3.0 → metalist-0.3.2}/app/services/note_service.py +0 -0
  126. {metalist-0.3.0 → metalist-0.3.2}/app/services/ontology_rules_store.py +0 -0
  127. {metalist-0.3.0 → metalist-0.3.2}/app/services/query_service.py +0 -0
  128. {metalist-0.3.0 → metalist-0.3.2}/app/services/reminders.py +0 -0
  129. {metalist-0.3.0 → metalist-0.3.2}/app/services/root_sorting.py +0 -0
  130. {metalist-0.3.0 → metalist-0.3.2}/app/services/runtime_hardening.py +0 -0
  131. {metalist-0.3.0 → metalist-0.3.2}/app/services/search_history.py +0 -0
  132. {metalist-0.3.0 → metalist-0.3.2}/app/services/search_query.py +0 -0
  133. {metalist-0.3.0 → metalist-0.3.2}/app/services/search_text.py +0 -0
  134. {metalist-0.3.0 → metalist-0.3.2}/app/services/session_timeout_service.py +0 -0
  135. {metalist-0.3.0 → metalist-0.3.2}/app/services/shell_session_service.py +0 -0
  136. {metalist-0.3.0 → metalist-0.3.2}/app/services/snapshot.py +0 -0
  137. {metalist-0.3.0 → metalist-0.3.2}/app/services/store.py +0 -0
  138. {metalist-0.3.0 → metalist-0.3.2}/app/services/sync.py +0 -0
  139. {metalist-0.3.0 → metalist-0.3.2}/app/services/sync_state.py +0 -0
  140. {metalist-0.3.0 → metalist-0.3.2}/app/services/tab_state.py +0 -0
  141. {metalist-0.3.0 → metalist-0.3.2}/app/services/tag_case.py +0 -0
  142. {metalist-0.3.0 → metalist-0.3.2}/app/services/tag_ontology.py +0 -0
  143. {metalist-0.3.0 → metalist-0.3.2}/app/services/tag_rename.py +0 -0
  144. {metalist-0.3.0 → metalist-0.3.2}/app/services/test_reset.py +0 -0
  145. {metalist-0.3.0 → metalist-0.3.2}/app/services/tokens.py +0 -0
  146. {metalist-0.3.0 → metalist-0.3.2}/app/services/transaction_manager.py +0 -0
  147. {metalist-0.3.0 → metalist-0.3.2}/app/services/undo_service.py +0 -0
  148. {metalist-0.3.0 → metalist-0.3.2}/app/services/undo_state.py +0 -0
  149. {metalist-0.3.0 → metalist-0.3.2}/app/services/view_cache.py +0 -0
  150. {metalist-0.3.0 → metalist-0.3.2}/app/services/view_diff.py +0 -0
  151. {metalist-0.3.0 → metalist-0.3.2}/app/services/view_state.py +0 -0
  152. {metalist-0.3.0 → metalist-0.3.2}/app/static/config/command_palette_tags.json +0 -0
  153. {metalist-0.3.0 → metalist-0.3.2}/app/static/css/forms.css +0 -0
  154. {metalist-0.3.0 → metalist-0.3.2}/app/static/img/favicon.ico +0 -0
  155. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/locked.js +0 -0
  156. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/main.js +0 -0
  157. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/activity-tracker.js +0 -0
  158. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/api-client.js +0 -0
  159. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/async-result.js +0 -0
  160. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/client-state-migration.js +0 -0
  161. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
  162. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/command-palette/command-palette-controller.js +0 -0
  163. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/command-palette/endpoint-registry.js +0 -0
  164. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/command-palette/preferences-store.js +0 -0
  165. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/command-palette/tag-config-loader.js +0 -0
  166. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/command-palette/usage-store.js +0 -0
  167. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/config.js +0 -0
  168. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/connectivity-monitor.js +0 -0
  169. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/context-menu/context-menu-registry.js +0 -0
  170. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/context-menu/context-menu-service.js +0 -0
  171. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
  172. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/dom-utils-fixed.js +0 -0
  173. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/dom-utils.js +0 -0
  174. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/editor-commands.js +0 -0
  175. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/editor-selection.js +0 -0
  176. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/editor-toolbar.js +0 -0
  177. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/error-handler.js +0 -0
  178. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/error-overlay.js +0 -0
  179. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/location-flags.js +0 -0
  180. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
  181. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/backup-restore-modal.js +0 -0
  182. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/backup-result-modal.js +0 -0
  183. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
  184. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/backup-settings-modal.js +0 -0
  185. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/base-modal.js +0 -0
  186. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/delete-namespace-modal.js +0 -0
  187. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/delete-namespace-validation.js +0 -0
  188. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/help-modal.js +0 -0
  189. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/memory-modal.js +0 -0
  190. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/namespace-loading-page.js +0 -0
  191. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/ontology-modal.js +0 -0
  192. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/password-modal.js +0 -0
  193. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/prioritize-modal.js +0 -0
  194. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/random-password-modal.js +0 -0
  195. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/reminder-modal.js +0 -0
  196. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
  197. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
  198. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/sound-manager-modal.js +0 -0
  199. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/modals/version-info-modal.js +0 -0
  200. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/actions/content-actions.js +0 -0
  201. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
  202. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/actions/note-actions.js +0 -0
  203. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
  204. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
  205. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/actions/ui-actions.js +0 -0
  206. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/events/focus-events.js +0 -0
  207. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
  208. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
  209. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/events/mouse-events.js +0 -0
  210. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
  211. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/mode-context.js +0 -0
  212. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/mode-logger.js +0 -0
  213. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
  214. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
  215. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
  216. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
  217. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
  218. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
  219. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -0
  220. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/date-filter-indicator-service.js +0 -0
  221. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/date-filter-service.js +0 -0
  222. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/differential-view-service.js +0 -0
  223. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
  224. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
  225. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
  226. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
  227. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
  228. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
  229. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +0 -0
  230. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +0 -0
  231. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
  232. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
  233. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
  234. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
  235. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
  236. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
  237. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
  238. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
  239. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
  240. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
  241. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
  242. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
  243. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/rhs-panel-service.js +0 -0
  244. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
  245. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
  246. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
  247. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
  248. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
  249. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
  250. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
  251. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
  252. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
  253. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
  254. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
  255. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
  256. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
  257. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
  258. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
  259. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -0
  260. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +0 -0
  261. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
  262. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
  263. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/password-generator.js +0 -0
  264. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/reminder-store.js +0 -0
  265. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/reminder-surface-service.js +0 -0
  266. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/selection-markers.js +0 -0
  267. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/session-auth.js +0 -0
  268. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/sound-service.js +0 -0
  269. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/tag-token.js +0 -0
  270. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/modules/uuid.js +0 -0
  271. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/namespace-deleted.js +0 -0
  272. {metalist-0.3.0 → metalist-0.3.2}/app/static/js/vendor/markdown-it.min.js +0 -0
  273. {metalist-0.3.0 → metalist-0.3.2}/app/static/video/login-startup.mp4 +0 -0
  274. {metalist-0.3.0 → metalist-0.3.2}/app/templates/base.html +0 -0
  275. {metalist-0.3.0 → metalist-0.3.2}/app/templates/index.html +0 -0
  276. {metalist-0.3.0 → metalist-0.3.2}/app/templates/locked.html +0 -0
  277. {metalist-0.3.0 → metalist-0.3.2}/app/templates/maintenance.html +0 -0
  278. {metalist-0.3.0 → metalist-0.3.2}/app/templates/namespace_deleted.html +0 -0
  279. {metalist-0.3.0 → metalist-0.3.2}/app/templates/notes_list.html +0 -0
  280. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/__init__.py +0 -0
  281. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/alphabetize_root_notes.py +0 -0
  282. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/base.py +0 -0
  283. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/collapse.py +0 -0
  284. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/copy_note.py +0 -0
  285. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/create_child.py +0 -0
  286. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/create_note.py +0 -0
  287. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/create_sibling.py +0 -0
  288. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/delete_subtree.py +0 -0
  289. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/expand.py +0 -0
  290. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/indent.py +0 -0
  291. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/lock.py +0 -0
  292. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/move.py +0 -0
  293. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/move_to_top.py +0 -0
  294. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/outdent.py +0 -0
  295. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/paste_child.py +0 -0
  296. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/paste_sibling.py +0 -0
  297. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/prioritize.py +0 -0
  298. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/record_edit_mode.py +0 -0
  299. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/redo.py +0 -0
  300. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/rename_tag.py +0 -0
  301. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/reset_updated_at.py +0 -0
  302. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/run_shell.py +0 -0
  303. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/search_comment_autofill.py +0 -0
  304. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/search_context_tags.py +0 -0
  305. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/set_collapse_bulk.py +0 -0
  306. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/set_collapse_in_context.py +0 -0
  307. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/split_note.py +0 -0
  308. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/toggle_reference_mode.py +0 -0
  309. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/toggle_todo_done.py +0 -0
  310. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/undo.py +0 -0
  311. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/unformat_content.py +0 -0
  312. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/update_content.py +0 -0
  313. {metalist-0.3.0 → metalist-0.3.2}/app/usecases/view.py +0 -0
  314. {metalist-0.3.0 → metalist-0.3.2}/app/utils/__init__.py +0 -0
  315. {metalist-0.3.0 → metalist-0.3.2}/app/utils/encryption.py +0 -0
  316. {metalist-0.3.0 → metalist-0.3.2}/app/utils/text_utils.py +0 -0
  317. {metalist-0.3.0 → metalist-0.3.2}/convert-from-legacy.py +0 -0
  318. {metalist-0.3.0 → metalist-0.3.2}/mcp_client.py +0 -0
  319. {metalist-0.3.0 → metalist-0.3.2}/metalist.egg-info/dependency_links.txt +0 -0
  320. {metalist-0.3.0 → metalist-0.3.2}/metalist.egg-info/top_level.txt +0 -0
  321. {metalist-0.3.0 → metalist-0.3.2}/scripts/generate-lan-cert.sh +0 -0
  322. {metalist-0.3.0 → metalist-0.3.2}/setup.cfg +0 -0
@@ -1,29 +1,31 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metalist
3
- Version: 0.3.0
3
+ Version: 0.3.2
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
7
7
  Project-URL: Issues, https://github.com/evolvingstuff/metalist/issues
8
- Requires-Python: >=3.10
8
+ Requires-Python: <3.14,>=3.10
9
9
  Description-Content-Type: text/markdown
10
- Requires-Dist: argon2-cffi==23.1.0
11
- Requires-Dist: cryptography==42.0.5
12
- Requires-Dist: fastapi==0.110.0
10
+ Requires-Dist: argon2-cffi==25.1.0
11
+ Requires-Dist: cryptography==50.0.0
12
+ Requires-Dist: fastapi==0.141.1
13
+ Requires-Dist: httpx==0.28.1
13
14
  Requires-Dist: latex2mathml==3.81.0
14
- Requires-Dist: loguru==0.7.2
15
- Requires-Dist: Mako==1.3.2
16
- Requires-Dist: mutagen==1.47.0
17
- Requires-Dist: pydantic==2.9.2
18
- Requires-Dist: python-multipart==0.0.9
19
- Requires-Dist: starlette==0.36.3
15
+ Requires-Dist: loguru==0.7.3
16
+ Requires-Dist: Mako==1.3.12
17
+ Requires-Dist: mutagen==1.48.1
18
+ Requires-Dist: pydantic==2.13.4
19
+ Requires-Dist: python-multipart==0.0.32
20
+ Requires-Dist: starlette==1.3.1
21
+ Requires-Dist: tqdm==4.70.0
20
22
  Requires-Dist: tree-sitter==0.25.2
21
23
  Requires-Dist: tree-sitter-javascript==0.25.0
22
- Requires-Dist: uvicorn==0.27.1
24
+ Requires-Dist: uvicorn==0.52.0
23
25
  Provides-Extra: dev
24
- Requires-Dist: hypothesis==6.112.0; extra == "dev"
25
- Requires-Dist: pytest==8.0.0; extra == "dev"
26
- Requires-Dist: pytest-cov==4.1.0; extra == "dev"
26
+ Requires-Dist: hypothesis==6.164.0; extra == "dev"
27
+ Requires-Dist: pytest==9.1.1; extra == "dev"
28
+ Requires-Dist: pytest-cov==7.1.0; extra == "dev"
27
29
 
28
30
  # MetaList
29
31
 
@@ -257,11 +259,11 @@ uv tool install metalist
257
259
  metalist
258
260
  ```
259
261
 
260
- This repo now packages itself under the PyPI distribution name `metalist`. The remaining release step is publishing version `0.3.0` to the existing PyPI project.
262
+ This repo now packages itself under the PyPI distribution name `metalist`. Current releases support Python 3.10 through 3.13.
261
263
 
262
264
  Recommended release path:
263
265
  1. In the existing PyPI project `metalist`, configure GitHub Trusted Publishing for `evolvingstuff/metalist` and the workflow file `.github/workflows/publish-pypi.yml`.
264
- 2. Push a tag such as `v0.3.0`.
266
+ 2. Push a tag such as `v0.3.2`.
265
267
  3. After the GitHub Actions workflow completes, users can run it with `uvx metalist`, install it persistently with `uv tool install metalist`, or install it with `pip install metalist`.
266
268
 
267
269
  If `--input` is omitted, a file picker opens (when `tkinter` is available).
@@ -230,11 +230,11 @@ uv tool install metalist
230
230
  metalist
231
231
  ```
232
232
 
233
- This repo now packages itself under the PyPI distribution name `metalist`. The remaining release step is publishing version `0.3.0` to the existing PyPI project.
233
+ This repo now packages itself under the PyPI distribution name `metalist`. Current releases support Python 3.10 through 3.13.
234
234
 
235
235
  Recommended release path:
236
236
  1. In the existing PyPI project `metalist`, configure GitHub Trusted Publishing for `evolvingstuff/metalist` and the workflow file `.github/workflows/publish-pypi.yml`.
237
- 2. Push a tag such as `v0.3.0`.
237
+ 2. Push a tag such as `v0.3.2`.
238
238
  3. After the GitHub Actions workflow completes, users can run it with `uvx metalist`, install it persistently with `uv tool install metalist`, or install it with `pip install metalist`.
239
239
 
240
240
  If `--input` is omitted, a file picker opens (when `tkinter` is available).
@@ -408,7 +408,16 @@ def _verify_namespace_password(*, namespace: str, password: str) -> None:
408
408
  raise HTTPException(status_code=401, detail="Invalid password")
409
409
 
410
410
 
411
- def _run_hydration() -> None:
411
+ def _run_hydration(*, rebuild_required: bool) -> None:
412
+ hydration_state.set_phase(
413
+ phase="database_check",
414
+ message="Database is up to date",
415
+ total=1,
416
+ )
417
+ hydration_state.update(1)
418
+ if not rebuild_required:
419
+ hydration_state.finish()
420
+ return
412
421
  session = SafeSession()
413
422
  try:
414
423
  prefetched_rows = populate_cache_from_db(session)
@@ -431,7 +440,7 @@ def _on_hydration_done(future: Future) -> None:
431
440
  hydration_state.fail(str(error))
432
441
 
433
442
 
434
- def _start_hydration(first_load: bool) -> None:
443
+ def _start_hydration(*, first_load: bool, rebuild_required: bool) -> None:
435
444
  global _hydration_future
436
445
  with _hydration_lock:
437
446
  if _hydration_future is not None and not _hydration_future.done():
@@ -440,7 +449,10 @@ def _start_hydration(first_load: bool) -> None:
440
449
  first_load=first_load,
441
450
  message="Preparing encrypted data",
442
451
  )
443
- _hydration_future = _hydration_executor.submit(_run_hydration)
452
+ _hydration_future = _hydration_executor.submit(
453
+ _run_hydration,
454
+ rebuild_required=rebuild_required,
455
+ )
444
456
  _hydration_future.add_done_callback(_on_hydration_done)
445
457
 
446
458
 
@@ -449,7 +461,7 @@ def _build_hydration_status() -> HydrationStatusResponse:
449
461
  if not auth_cache_state.cache_refresh_needed() and snapshot["status"] == "idle":
450
462
  snapshot["status"] = "ready"
451
463
  snapshot["phase"] = "complete"
452
- snapshot["message"] = "Hydration complete"
464
+ snapshot["message"] = "Workspace ready"
453
465
  snapshot["overall_percent"] = 100
454
466
  return HydrationStatusResponse(**snapshot)
455
467
 
@@ -624,6 +636,7 @@ def login(
624
636
  raise HTTPException(status_code=401, detail="Invalid password")
625
637
 
626
638
  dek = auth.unwrap_dek_for_password(payload.password)
639
+ auth.run_authenticated_database_migrations(dek=dek)
627
640
 
628
641
  set_session_dek(dek)
629
642
  ensure_rules_decrypted_and_compiled(token="")
@@ -632,17 +645,13 @@ def login(
632
645
  reminder_store.ensure_decrypted(token="")
633
646
  search_history_store.ensure_decrypted(token="")
634
647
 
635
- needs_hydration = auth_cache_state.cache_refresh_needed()
636
- if not note_store.loaded:
637
- needs_hydration = True
638
-
639
648
  token = token_service.create_token(_client_info(request), tab_id, dek=dek)
640
649
  set_auth_cookie(request=request, response=response, token=token)
641
650
  login_rate_limiter.record_success(rate_limit_key)
642
651
  clear_all_locks()
643
652
  return LoginResponse(
644
653
  message="Login successful",
645
- hydration_required=needs_hydration,
654
+ hydration_required=True,
646
655
  )
647
656
 
648
657
 
@@ -746,6 +755,11 @@ def auth_status(
746
755
  database_user_version = database_user_version_row[0]
747
756
  if not isinstance(database_user_version, int) or database_user_version < 0:
748
757
  raise RuntimeError(f"Invalid database user_version: {database_user_version!r}")
758
+ client_preferences: dict[str, str] = {}
759
+ if not bool(settings and settings.encryption_enabled):
760
+ client_preferences = load_client_preferences(token="")
761
+ elif token is not None:
762
+ client_preferences = load_client_preferences(token=token)
749
763
  return {
750
764
  "version": VERSION,
751
765
  "database_user_version": database_user_version,
@@ -760,14 +774,13 @@ def auth_status(
760
774
  "cache_ready": not auth_cache_state.cache_refresh_needed(),
761
775
  "namespace": ACTIVE_NAMESPACE,
762
776
  "link_title_revision": link_title_store.get_revision(),
763
- "client_preferences": load_client_preferences(),
777
+ "client_preferences": client_preferences,
764
778
  }
765
779
 
766
780
 
767
781
  @router.get("/client-state", response_model=ClientStateResponse)
768
782
  def get_client_state(token: Annotated[str, Depends(_require_auth)]):
769
- del token
770
- payload = load_client_state()
783
+ payload = load_client_state(token=token)
771
784
  return ClientStateResponse(**payload)
772
785
 
773
786
 
@@ -777,8 +790,7 @@ def put_client_preferences(
777
790
  payload: ClientPreferencesUpdateRequest,
778
791
  token: Annotated[str, Depends(_require_auth)],
779
792
  ):
780
- del token
781
- normalized = save_client_preferences(preferences=payload.preferences)
793
+ normalized = save_client_preferences(preferences=payload.preferences, token=token)
782
794
  return ClientPreferencesResponse(preferences=normalized)
783
795
 
784
796
 
@@ -788,8 +800,10 @@ def put_command_palette_usage(
788
800
  payload: CommandPaletteUsageUpdateRequest,
789
801
  token: Annotated[str, Depends(_require_auth)],
790
802
  ):
791
- del token
792
- normalized = save_command_palette_usage(usage_state=payload.command_palette_usage)
803
+ normalized = save_command_palette_usage(
804
+ usage_state=payload.command_palette_usage,
805
+ token=token,
806
+ )
793
807
  return CommandPaletteUsageResponse(command_palette_usage=normalized)
794
808
 
795
809
 
@@ -1067,8 +1081,10 @@ def hydrate_cache(
1067
1081
  if not note_store.loaded:
1068
1082
  needs_hydration = True
1069
1083
 
1070
- if needs_hydration:
1071
- _start_hydration(first_load=auth_cache_state.cache_refresh_needed())
1084
+ _start_hydration(
1085
+ first_load=auth_cache_state.cache_refresh_needed(),
1086
+ rebuild_required=needs_hydration,
1087
+ )
1072
1088
 
1073
1089
  return _build_hydration_status()
1074
1090
 
@@ -5,8 +5,9 @@ from app.server_runtime import resolve_api_prefix
5
5
  from app.server_runtime import resolve_database_runtime_config
6
6
  from app.server_runtime import resolve_default_database_path
7
7
  from app.server_runtime import resolve_v1_api_prefix
8
+ from app.version import __version__
8
9
 
9
- VERSION = "0.3.0"
10
+ VERSION = __version__
10
11
 
11
12
 
12
13
  def _env_flag(name: str, default: bool) -> bool:
@@ -0,0 +1,166 @@
1
+ """Ordered, transactional database migrations for namespace databases."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ import sqlite3
8
+
9
+ from app.services.encryption import EncryptionService
10
+
11
+
12
+ CURRENT_DATABASE_VERSION = 1
13
+
14
+
15
+ @dataclass(frozen=True, slots=True)
16
+ class MigrationResult:
17
+ initial_version: int
18
+ final_version: int
19
+ applied_versions: tuple[int, ...]
20
+ rewritten_payload_count: int
21
+
22
+
23
+ _CLIENT_STATE_PAYLOADS = (
24
+ (
25
+ "client_preferences_json",
26
+ "client_preferences_encryption_nonce",
27
+ "client_preferences_encryption_tag",
28
+ ),
29
+ (
30
+ "command_palette_usage_json",
31
+ "command_palette_usage_encryption_nonce",
32
+ "command_palette_usage_encryption_tag",
33
+ ),
34
+ (
35
+ "tag_prefix_settings_json",
36
+ "tag_prefix_settings_encryption_nonce",
37
+ "tag_prefix_settings_encryption_tag",
38
+ ),
39
+ )
40
+
41
+
42
+ def read_database_version(connection: sqlite3.Connection) -> int:
43
+ row = connection.execute("PRAGMA user_version").fetchone()
44
+ if row is None:
45
+ raise RuntimeError("Database user_version PRAGMA returned no row")
46
+ version = row[0]
47
+ if not isinstance(version, int) or version < 0:
48
+ raise RuntimeError(f"Invalid database user_version: {version!r}")
49
+ return version
50
+
51
+
52
+ def _table_columns(connection: sqlite3.Connection, *, table: str) -> set[str]:
53
+ return {
54
+ str(row[1])
55
+ for row in connection.execute(f"PRAGMA table_info({table})").fetchall()
56
+ }
57
+
58
+
59
+ def _assert_client_state_schema(connection: sqlite3.Connection) -> None:
60
+ columns = _table_columns(connection, table="app_settings")
61
+ required = {column for payload in _CLIENT_STATE_PAYLOADS for column in payload}
62
+ missing = sorted(required - columns)
63
+ if missing:
64
+ raise RuntimeError(f"Database migration requires missing columns: {missing}")
65
+
66
+
67
+ def _rewrite_payload_for_encrypted_namespace(
68
+ *,
69
+ connection: sqlite3.Connection,
70
+ encryption_service: EncryptionService,
71
+ value_column: str,
72
+ nonce_column: str,
73
+ tag_column: str,
74
+ ) -> int:
75
+ row = connection.execute(
76
+ f"SELECT {value_column}, {nonce_column}, {tag_column} FROM app_settings WHERE id = 1"
77
+ ).fetchone()
78
+ if row is None:
79
+ raise RuntimeError("Database migration requires app_settings row id=1")
80
+ value, nonce, tag = row
81
+ if (nonce is None) != (tag is None):
82
+ raise RuntimeError(f"{value_column} has incomplete encryption metadata")
83
+ if value is None or value == "":
84
+ if nonce is not None:
85
+ raise RuntimeError(f"{value_column} has encryption metadata without a payload")
86
+ return 0
87
+ if not isinstance(value, str):
88
+ raise RuntimeError(f"{value_column} must be stored as text")
89
+ if nonce is not None:
90
+ encryption_service.decrypt_from_storage(value, nonce, tag)
91
+ return 0
92
+ ciphertext, next_nonce, next_tag = encryption_service.encrypt_for_storage(value)
93
+ connection.execute(
94
+ f"""
95
+ UPDATE app_settings
96
+ SET {value_column} = ?, {nonce_column} = ?, {tag_column} = ?
97
+ WHERE id = 1
98
+ """,
99
+ (ciphertext, next_nonce, next_tag),
100
+ )
101
+ return 1
102
+
103
+
104
+ def _migration_0_to_1(
105
+ *,
106
+ connection: sqlite3.Connection,
107
+ encryption_enabled: bool,
108
+ encryption_service: EncryptionService | None,
109
+ ) -> int:
110
+ _assert_client_state_schema(connection)
111
+ if not encryption_enabled:
112
+ return 0
113
+ if encryption_service is None or encryption_service.dek is None:
114
+ raise RuntimeError("Database migration 0→1 requires the namespace DEK")
115
+ rewritten_count = 0
116
+ for value_column, nonce_column, tag_column in _CLIENT_STATE_PAYLOADS:
117
+ rewritten_count += _rewrite_payload_for_encrypted_namespace(
118
+ connection=connection,
119
+ encryption_service=encryption_service,
120
+ value_column=value_column,
121
+ nonce_column=nonce_column,
122
+ tag_column=tag_column,
123
+ )
124
+ return rewritten_count
125
+
126
+
127
+ _MIGRATIONS: dict[
128
+ int,
129
+ Callable[..., int],
130
+ ] = {
131
+ 0: _migration_0_to_1,
132
+ }
133
+
134
+
135
+ def run_database_migrations(
136
+ *,
137
+ connection: sqlite3.Connection,
138
+ encryption_enabled: bool,
139
+ encryption_service: EncryptionService | None,
140
+ ) -> MigrationResult:
141
+ initial_version = read_database_version(connection)
142
+ if initial_version > CURRENT_DATABASE_VERSION:
143
+ raise RuntimeError(
144
+ f"Database version {initial_version} is newer than supported version "
145
+ f"{CURRENT_DATABASE_VERSION}"
146
+ )
147
+ version = initial_version
148
+ applied_versions: list[int] = []
149
+ rewritten_payload_count = 0
150
+ while version < CURRENT_DATABASE_VERSION:
151
+ if version not in _MIGRATIONS:
152
+ raise RuntimeError(f"No database migration registered from version {version}")
153
+ rewritten_payload_count += _MIGRATIONS[version](
154
+ connection=connection,
155
+ encryption_enabled=encryption_enabled,
156
+ encryption_service=encryption_service,
157
+ )
158
+ version += 1
159
+ connection.execute(f"PRAGMA user_version = {version}")
160
+ applied_versions.append(version)
161
+ return MigrationResult(
162
+ initial_version=initial_version,
163
+ final_version=version,
164
+ applied_versions=tuple(applied_versions),
165
+ rewritten_payload_count=rewritten_payload_count,
166
+ )
@@ -64,7 +64,14 @@ CREATE TABLE IF NOT EXISTS {APP_SETTINGS_TABLE} (
64
64
  backup_settings_encryption_nonce BLOB,
65
65
  backup_settings_encryption_tag BLOB,
66
66
  client_preferences_json TEXT,
67
+ client_preferences_encryption_nonce BLOB,
68
+ client_preferences_encryption_tag BLOB,
67
69
  command_palette_usage_json TEXT,
70
+ command_palette_usage_encryption_nonce BLOB,
71
+ command_palette_usage_encryption_tag BLOB,
72
+ tag_prefix_settings_json TEXT,
73
+ tag_prefix_settings_encryption_nonce BLOB,
74
+ tag_prefix_settings_encryption_tag BLOB,
68
75
  session_timeout_minutes INTEGER,
69
76
  created_at TEXT NOT NULL,
70
77
  updated_at TEXT NOT NULL
@@ -210,7 +217,14 @@ def initialize_schema(connection: Connection) -> None:
210
217
  "backup_settings_encryption_nonce": "BLOB",
211
218
  "backup_settings_encryption_tag": "BLOB",
212
219
  "client_preferences_json": "TEXT",
220
+ "client_preferences_encryption_nonce": "BLOB",
221
+ "client_preferences_encryption_tag": "BLOB",
213
222
  "command_palette_usage_json": "TEXT",
223
+ "command_palette_usage_encryption_nonce": "BLOB",
224
+ "command_palette_usage_encryption_tag": "BLOB",
225
+ "tag_prefix_settings_json": "TEXT",
226
+ "tag_prefix_settings_encryption_nonce": "BLOB",
227
+ "tag_prefix_settings_encryption_tag": "BLOB",
214
228
  "session_timeout_minutes": "INTEGER",
215
229
  },
216
230
  )
@@ -51,7 +51,22 @@ def fetch_settings(connection: GuardedConnection | sqlite3.Connection) -> Option
51
51
  "backup_settings_encryption_nonce": row["backup_settings_encryption_nonce"],
52
52
  "backup_settings_encryption_tag": row["backup_settings_encryption_tag"],
53
53
  "client_preferences_json": row["client_preferences_json"],
54
+ "client_preferences_encryption_nonce": row["client_preferences_encryption_nonce"],
55
+ "client_preferences_encryption_tag": row["client_preferences_encryption_tag"],
54
56
  "command_palette_usage_json": row["command_palette_usage_json"],
57
+ "command_palette_usage_encryption_nonce": row[
58
+ "command_palette_usage_encryption_nonce"
59
+ ],
60
+ "command_palette_usage_encryption_tag": row[
61
+ "command_palette_usage_encryption_tag"
62
+ ],
63
+ "tag_prefix_settings_json": row["tag_prefix_settings_json"],
64
+ "tag_prefix_settings_encryption_nonce": row[
65
+ "tag_prefix_settings_encryption_nonce"
66
+ ],
67
+ "tag_prefix_settings_encryption_tag": row[
68
+ "tag_prefix_settings_encryption_tag"
69
+ ],
55
70
  "session_timeout_minutes": row["session_timeout_minutes"],
56
71
  "created_at": datetime.fromisoformat(row["created_at"]),
57
72
  "updated_at": datetime.fromisoformat(row["updated_at"]),
@@ -168,6 +183,8 @@ def update_client_preferences_json(
168
183
  connection: GuardedConnection | sqlite3.Connection,
169
184
  *,
170
185
  client_preferences_json: str,
186
+ client_preferences_encryption_nonce: bytes | None,
187
+ client_preferences_encryption_tag: bytes | None,
171
188
  ) -> None:
172
189
  if not isinstance(client_preferences_json, str):
173
190
  raise TypeError("client_preferences_json must be a string")
@@ -177,11 +194,15 @@ def update_client_preferences_json(
177
194
  f"""
178
195
  UPDATE {APP_SETTINGS_TABLE}
179
196
  SET client_preferences_json = ?,
197
+ client_preferences_encryption_nonce = ?,
198
+ client_preferences_encryption_tag = ?,
180
199
  updated_at = ?
181
200
  WHERE id = 1
182
201
  """,
183
202
  (
184
203
  client_preferences_json,
204
+ client_preferences_encryption_nonce,
205
+ client_preferences_encryption_tag,
185
206
  _serialize_datetime(datetime.now(timezone.utc)),
186
207
  ),
187
208
  )
@@ -191,6 +212,8 @@ def update_command_palette_usage_json(
191
212
  connection: GuardedConnection | sqlite3.Connection,
192
213
  *,
193
214
  command_palette_usage_json: str,
215
+ command_palette_usage_encryption_nonce: bytes | None,
216
+ command_palette_usage_encryption_tag: bytes | None,
194
217
  ) -> None:
195
218
  if not isinstance(command_palette_usage_json, str):
196
219
  raise TypeError("command_palette_usage_json must be a string")
@@ -200,11 +223,44 @@ def update_command_palette_usage_json(
200
223
  f"""
201
224
  UPDATE {APP_SETTINGS_TABLE}
202
225
  SET command_palette_usage_json = ?,
226
+ command_palette_usage_encryption_nonce = ?,
227
+ command_palette_usage_encryption_tag = ?,
203
228
  updated_at = ?
204
229
  WHERE id = 1
205
230
  """,
206
231
  (
207
232
  command_palette_usage_json,
233
+ command_palette_usage_encryption_nonce,
234
+ command_palette_usage_encryption_tag,
235
+ _serialize_datetime(datetime.now(timezone.utc)),
236
+ ),
237
+ )
238
+
239
+
240
+ def update_tag_prefix_settings_json(
241
+ connection: GuardedConnection | sqlite3.Connection,
242
+ *,
243
+ tag_prefix_settings_json: str,
244
+ tag_prefix_settings_encryption_nonce: bytes | None,
245
+ tag_prefix_settings_encryption_tag: bytes | None,
246
+ ) -> None:
247
+ if not isinstance(tag_prefix_settings_json, str):
248
+ raise TypeError("tag_prefix_settings_json must be a string")
249
+
250
+ conn = _conn(connection)
251
+ conn.execute(
252
+ f"""
253
+ UPDATE {APP_SETTINGS_TABLE}
254
+ SET tag_prefix_settings_json = ?,
255
+ tag_prefix_settings_encryption_nonce = ?,
256
+ tag_prefix_settings_encryption_tag = ?,
257
+ updated_at = ?
258
+ WHERE id = 1
259
+ """,
260
+ (
261
+ tag_prefix_settings_json,
262
+ tag_prefix_settings_encryption_nonce,
263
+ tag_prefix_settings_encryption_tag,
208
264
  _serialize_datetime(datetime.now(timezone.utc)),
209
265
  ),
210
266
  )