MetaList 0.9.0__tar.gz → 0.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (525) hide show
  1. {metalist-0.9.0 → metalist-0.10.0}/PKG-INFO +9 -1
  2. {metalist-0.9.0 → metalist-0.10.0}/README.md +8 -0
  3. {metalist-0.9.0 → metalist-0.10.0}/app/api/note_requests.py +13 -0
  4. metalist-0.10.0/app/api/routes/files.py +244 -0
  5. {metalist-0.9.0 → metalist-0.10.0}/app/api/upload_limits.py +13 -1
  6. metalist-0.10.0/app/db/editable_files_migration.py +50 -0
  7. metalist-0.10.0/app/db/file_schema.py +58 -0
  8. {metalist-0.9.0 → metalist-0.10.0}/app/db/files_sql.py +161 -2
  9. {metalist-0.9.0 → metalist-0.10.0}/app/db/migrations.py +2 -0
  10. {metalist-0.9.0 → metalist-0.10.0}/app/db/version.py +1 -1
  11. {metalist-0.9.0 → metalist-0.10.0}/app/encryption_audit.py +63 -8
  12. {metalist-0.9.0 → metalist-0.10.0}/app/main.py +27 -0
  13. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/help_catalog.py +1 -1
  14. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-menus.md +2 -1
  15. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-notes.md +1 -1
  16. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-references.md +14 -1
  17. {metalist-0.9.0 → metalist-0.10.0}/app/services/backlinks.py +2 -1
  18. {metalist-0.9.0 → metalist-0.10.0}/app/services/backup_service.py +3 -1
  19. {metalist-0.9.0 → metalist-0.10.0}/app/services/client_state_service.py +2 -0
  20. {metalist-0.9.0 → metalist-0.10.0}/app/services/embedded_references.py +199 -4
  21. metalist-0.10.0/app/services/file_edit_sessions.py +81 -0
  22. {metalist-0.9.0 → metalist-0.10.0}/app/services/file_registry.py +7 -0
  23. {metalist-0.9.0 → metalist-0.10.0}/app/services/file_storage.py +403 -1
  24. {metalist-0.9.0 → metalist-0.10.0}/app/services/html_export.py +9 -1
  25. {metalist-0.9.0 → metalist-0.10.0}/app/services/image_size_formatting.py +1 -1
  26. {metalist-0.9.0 → metalist-0.10.0}/app/services/undo_state.py +59 -1
  27. {metalist-0.9.0 → metalist-0.10.0}/app/static/config/agent-menu-actions.json +6 -0
  28. {metalist-0.9.0 → metalist-0.10.0}/app/static/config/command_palette_tags.json +5 -1
  29. {metalist-0.9.0 → metalist-0.10.0}/app/static/css/main.css +515 -116
  30. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/api-client.js +191 -1
  31. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/command-palette-controller.js +30 -75
  32. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/endpoint-registry.js +8 -0
  33. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/note-layout-preferences.js +19 -0
  34. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/tag-config-loader.js +94 -0
  35. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/config.js +6 -0
  36. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/context-menu/context-menu-registry.js +78 -1
  37. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/context-menu/context-menu-service.js +6 -0
  38. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/editor-selection.js +2 -1
  39. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/editor-toolbar.js +2 -1
  40. metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-actions.js +93 -0
  41. metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-autosave.js +159 -0
  42. metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-editor-service.js +388 -0
  43. metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-editor-state.js +35 -0
  44. metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-loader.js +50 -0
  45. metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-preview-service.js +163 -0
  46. metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-scene.js +89 -0
  47. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/expected-errors.js +20 -0
  48. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/note-layout-appearance-modal.js +18 -4
  49. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/context-menu-events.js +17 -2
  50. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/focus-events.js +3 -2
  51. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/keyboard-events.js +12 -9
  52. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/mouse-events.js +41 -15
  53. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/differential-view-service.js +23 -1
  54. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/floating-note-service.js +2 -0
  55. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +6 -0
  56. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-fullscreen-service.js +2 -0
  57. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +5 -4
  58. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +28 -0
  59. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/excalidraw.css +1 -0
  60. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Bold.woff2 +0 -0
  61. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Medium.woff2 +0 -0
  62. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Regular.woff2 +0 -0
  63. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-SemiBold.woff2 +0 -0
  64. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Cascadia/CascadiaCode-Regular.woff2 +0 -0
  65. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2 +0 -0
  66. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.woff2 +0 -0
  67. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.woff2 +0 -0
  68. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.woff2 +0 -0
  69. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.woff2 +0 -0
  70. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.woff2 +0 -0
  71. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-41b173a47b57366892116a575a43e2b6.woff2 +0 -0
  72. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.woff2 +0 -0
  73. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2 +0 -0
  74. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.woff2 +0 -0
  75. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.woff2 +0 -0
  76. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 +0 -0
  77. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 +0 -0
  78. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2 +0 -0
  79. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2 +0 -0
  80. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2 +0 -0
  81. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2 +0 -0
  82. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2 +0 -0
  83. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Virgil/Virgil-Regular.woff2 +0 -0
  84. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1.LICENSE.txt +3135 -0
  85. metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1.min.js +208 -0
  86. metalist-0.10.0/app/usecases/file_edit_session.py +110 -0
  87. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/resize_image.py +1 -1
  88. {metalist-0.9.0 → metalist-0.10.0}/app/version.py +1 -1
  89. {metalist-0.9.0 → metalist-0.10.0}/metalist.egg-info/PKG-INFO +9 -1
  90. {metalist-0.9.0 → metalist-0.10.0}/metalist.egg-info/SOURCES.txt +37 -0
  91. metalist-0.9.0/app/api/routes/files.py +0 -95
  92. metalist-0.9.0/app/db/file_schema.py +0 -28
  93. {metalist-0.9.0 → metalist-0.10.0}/MANIFEST.in +0 -0
  94. {metalist-0.9.0 → metalist-0.10.0}/app/__init__.py +0 -0
  95. {metalist-0.9.0 → metalist-0.10.0}/app/api/__init__.py +0 -0
  96. {metalist-0.9.0 → metalist-0.10.0}/app/api/deps.py +0 -0
  97. {metalist-0.9.0 → metalist-0.10.0}/app/api/dev.py +0 -0
  98. {metalist-0.9.0 → metalist-0.10.0}/app/api/middleware/auth.py +0 -0
  99. {metalist-0.9.0 → metalist-0.10.0}/app/api/request_auth.py +0 -0
  100. {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/ai.py +0 -0
  101. {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/app_updates.py +0 -0
  102. {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/auth.py +0 -0
  103. {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/backups.py +0 -0
  104. {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/notes.py +0 -0
  105. {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/ontology.py +0 -0
  106. {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/reminders.py +0 -0
  107. {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/remote_images.py +0 -0
  108. {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/test.py +0 -0
  109. {metalist-0.9.0 → metalist-0.10.0}/app/api/transactions.py +0 -0
  110. {metalist-0.9.0 → metalist-0.10.0}/app/config.py +0 -0
  111. {metalist-0.9.0 → metalist-0.10.0}/app/data_directory.py +0 -0
  112. {metalist-0.9.0 → metalist-0.10.0}/app/db/__init__.py +0 -0
  113. {metalist-0.9.0 → metalist-0.10.0}/app/db/backup_settings_sql.py +0 -0
  114. {metalist-0.9.0 → metalist-0.10.0}/app/db/engine.py +0 -0
  115. {metalist-0.9.0 → metalist-0.10.0}/app/db/file_session.py +0 -0
  116. {metalist-0.9.0 → metalist-0.10.0}/app/db/link_titles_sql.py +0 -0
  117. {metalist-0.9.0 → metalist-0.10.0}/app/db/live_recovery.py +0 -0
  118. {metalist-0.9.0 → metalist-0.10.0}/app/db/notes_sql.py +0 -0
  119. {metalist-0.9.0 → metalist-0.10.0}/app/db/ontology_rules_sql.py +0 -0
  120. {metalist-0.9.0 → metalist-0.10.0}/app/db/reminders_sql.py +0 -0
  121. {metalist-0.9.0 → metalist-0.10.0}/app/db/retire_sounds.py +0 -0
  122. {metalist-0.9.0 → metalist-0.10.0}/app/db/schema.py +0 -0
  123. {metalist-0.9.0 → metalist-0.10.0}/app/db/search_history_sql.py +0 -0
  124. {metalist-0.9.0 → metalist-0.10.0}/app/db/session.py +0 -0
  125. {metalist-0.9.0 → metalist-0.10.0}/app/db/settings_sql.py +0 -0
  126. {metalist-0.9.0 → metalist-0.10.0}/app/db/tab_state_sql.py +0 -0
  127. {metalist-0.9.0 → metalist-0.10.0}/app/exception_boundaries.py +0 -0
  128. {metalist-0.9.0 → metalist-0.10.0}/app/https_proxy.py +0 -0
  129. {metalist-0.9.0 → metalist-0.10.0}/app/models/commands.py +0 -0
  130. {metalist-0.9.0 → metalist-0.10.0}/app/models/database.py +0 -0
  131. {metalist-0.9.0 → metalist-0.10.0}/app/models/entities.py +0 -0
  132. {metalist-0.9.0 → metalist-0.10.0}/app/models/enums.py +0 -0
  133. {metalist-0.9.0 → metalist-0.10.0}/app/models/linked_list.py +0 -0
  134. {metalist-0.9.0 → metalist-0.10.0}/app/models/list_operations.py +0 -0
  135. {metalist-0.9.0 → metalist-0.10.0}/app/models/list_traversal.py +0 -0
  136. {metalist-0.9.0 → metalist-0.10.0}/app/models/note_crud.py +0 -0
  137. {metalist-0.9.0 → metalist-0.10.0}/app/models/utils.py +0 -0
  138. {metalist-0.9.0 → metalist-0.10.0}/app/network_settings.py +0 -0
  139. {metalist-0.9.0 → metalist-0.10.0}/app/presentation/__init__.py +0 -0
  140. {metalist-0.9.0 → metalist-0.10.0}/app/presentation/render/__init__.py +0 -0
  141. {metalist-0.9.0 → metalist-0.10.0}/app/presentation/render/note_renderer.py +0 -0
  142. {metalist-0.9.0 → metalist-0.10.0}/app/presentation/templates.py +0 -0
  143. {metalist-0.9.0 → metalist-0.10.0}/app/security/authenticated_logging.py +0 -0
  144. {metalist-0.9.0 → metalist-0.10.0}/app/security/encryption.py +0 -0
  145. {metalist-0.9.0 → metalist-0.10.0}/app/security/http_error_diagnostics.py +0 -0
  146. {metalist-0.9.0 → metalist-0.10.0}/app/security/http_headers.py +0 -0
  147. {metalist-0.9.0 → metalist-0.10.0}/app/security/note_html.py +0 -0
  148. {metalist-0.9.0 → metalist-0.10.0}/app/security/request_boundary.py +0 -0
  149. {metalist-0.9.0 → metalist-0.10.0}/app/security/request_hosts.py +0 -0
  150. {metalist-0.9.0 → metalist-0.10.0}/app/security/sensitive_cache.py +0 -0
  151. {metalist-0.9.0 → metalist-0.10.0}/app/security/sensitive_logging.py +0 -0
  152. {metalist-0.9.0 → metalist-0.10.0}/app/security/shell_execution.py +0 -0
  153. {metalist-0.9.0 → metalist-0.10.0}/app/security/validation_errors.py +0 -0
  154. {metalist-0.9.0 → metalist-0.10.0}/app/server_runtime.py +0 -0
  155. {metalist-0.9.0 → metalist-0.10.0}/app/services/__init__.py +0 -0
  156. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/__init__.py +0 -0
  157. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/actions.py +0 -0
  158. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/cloud_privacy.py +0 -0
  159. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/context.py +0 -0
  160. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/evidence_serialization.py +0 -0
  161. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/history.py +0 -0
  162. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/inference.py +0 -0
  163. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/investigation.py +0 -0
  164. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/judging.py +0 -0
  165. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/menu_actions.py +0 -0
  166. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/model_policy.py +0 -0
  167. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/openai_cost_tracking.py +0 -0
  168. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/openai_inference.py +0 -0
  169. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/permissions.py +0 -0
  170. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/prompt_settings.py +0 -0
  171. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/prompts/__init__.py +0 -0
  172. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/prompts/final-response.md +0 -0
  173. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/prompts/help-response.md +0 -0
  174. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/prompts/system.md +0 -0
  175. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/prompts/tool-result.md +0 -0
  176. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/retrieval_settings.py +0 -0
  177. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/runtime.py +0 -0
  178. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/scope.py +0 -0
  179. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skill_settings.py +0 -0
  180. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/__init__.py +0 -0
  181. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-ai.md +0 -0
  182. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-data.md +0 -0
  183. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-formatting.md +0 -0
  184. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-privacy.md +0 -0
  185. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-reminders.md +0 -0
  186. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-search.md +0 -0
  187. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-tags.md +0 -0
  188. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/scoped-investigation.md +0 -0
  189. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/staged-summary.md +0 -0
  190. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/tag-proposals.md +0 -0
  191. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/web-browsing.md +0 -0
  192. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/staged_summary.py +0 -0
  193. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/structured_inference.py +0 -0
  194. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/tagging.py +0 -0
  195. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/tagging_run.py +0 -0
  196. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/token_estimation.py +0 -0
  197. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/tools.py +0 -0
  198. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/trace.py +0 -0
  199. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/web_capabilities.py +0 -0
  200. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/web_evidence.py +0 -0
  201. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/web_fetch.py +0 -0
  202. {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/web_settings.py +0 -0
  203. {metalist-0.9.0 → metalist-0.10.0}/app/services/ai_chat.py +0 -0
  204. {metalist-0.9.0 → metalist-0.10.0}/app/services/ai_chat_rendering.py +0 -0
  205. {metalist-0.9.0 → metalist-0.10.0}/app/services/ai_chat_stream.py +0 -0
  206. {metalist-0.9.0 → metalist-0.10.0}/app/services/app_update_worker.py +0 -0
  207. {metalist-0.9.0 → metalist-0.10.0}/app/services/app_updates.py +0 -0
  208. {metalist-0.9.0 → metalist-0.10.0}/app/services/auth.py +0 -0
  209. {metalist-0.9.0 → metalist-0.10.0}/app/services/auth_cache_state.py +0 -0
  210. {metalist-0.9.0 → metalist-0.10.0}/app/services/auth_service.py +0 -0
  211. {metalist-0.9.0 → metalist-0.10.0}/app/services/backlink_index.py +0 -0
  212. {metalist-0.9.0 → metalist-0.10.0}/app/services/backup_settings_service.py +0 -0
  213. {metalist-0.9.0 → metalist-0.10.0}/app/services/bulk_operation.py +0 -0
  214. {metalist-0.9.0 → metalist-0.10.0}/app/services/content_cache.py +0 -0
  215. {metalist-0.9.0 → metalist-0.10.0}/app/services/content_formatting.py +0 -0
  216. {metalist-0.9.0 → metalist-0.10.0}/app/services/diagnostics.py +0 -0
  217. {metalist-0.9.0 → metalist-0.10.0}/app/services/encryption.py +0 -0
  218. {metalist-0.9.0 → metalist-0.10.0}/app/services/exception_capture.py +0 -0
  219. {metalist-0.9.0 → metalist-0.10.0}/app/services/footnote_layout.py +0 -0
  220. {metalist-0.9.0 → metalist-0.10.0}/app/services/footnote_rendering.py +0 -0
  221. {metalist-0.9.0 → metalist-0.10.0}/app/services/hierarchy.py +0 -0
  222. {metalist-0.9.0 → metalist-0.10.0}/app/services/html_unformatting.py +0 -0
  223. {metalist-0.9.0 → metalist-0.10.0}/app/services/hydration_state.py +0 -0
  224. {metalist-0.9.0 → metalist-0.10.0}/app/services/inline_image_occurrences.py +0 -0
  225. {metalist-0.9.0 → metalist-0.10.0}/app/services/input_errors.py +0 -0
  226. {metalist-0.9.0 → metalist-0.10.0}/app/services/integrity.py +0 -0
  227. {metalist-0.9.0 → metalist-0.10.0}/app/services/latex_rendering.py +0 -0
  228. {metalist-0.9.0 → metalist-0.10.0}/app/services/link_titles.py +0 -0
  229. {metalist-0.9.0 → metalist-0.10.0}/app/services/login_rate_limit.py +0 -0
  230. {metalist-0.9.0 → metalist-0.10.0}/app/services/maintenance_mode.py +0 -0
  231. {metalist-0.9.0 → metalist-0.10.0}/app/services/markdown_rendering.py +0 -0
  232. {metalist-0.9.0 → metalist-0.10.0}/app/services/namespace_deletion_jobs.py +0 -0
  233. {metalist-0.9.0 → metalist-0.10.0}/app/services/namespace_deletion_worker.py +0 -0
  234. {metalist-0.9.0 → metalist-0.10.0}/app/services/namespace_rename_jobs.py +0 -0
  235. {metalist-0.9.0 → metalist-0.10.0}/app/services/namespace_rename_worker.py +0 -0
  236. {metalist-0.9.0 → metalist-0.10.0}/app/services/namespace_runtime_guard.py +0 -0
  237. {metalist-0.9.0 → metalist-0.10.0}/app/services/namespace_switcher.py +0 -0
  238. {metalist-0.9.0 → metalist-0.10.0}/app/services/note_fullscreen.py +0 -0
  239. {metalist-0.9.0 → metalist-0.10.0}/app/services/note_image_tags.py +0 -0
  240. {metalist-0.9.0 → metalist-0.10.0}/app/services/note_ordering.py +0 -0
  241. {metalist-0.9.0 → metalist-0.10.0}/app/services/note_store.py +0 -0
  242. {metalist-0.9.0 → metalist-0.10.0}/app/services/ontology_rules_store.py +0 -0
  243. {metalist-0.9.0 → metalist-0.10.0}/app/services/openai_credentials.py +0 -0
  244. {metalist-0.9.0 → metalist-0.10.0}/app/services/password_note_fields.py +0 -0
  245. {metalist-0.9.0 → metalist-0.10.0}/app/services/public_http.py +0 -0
  246. {metalist-0.9.0 → metalist-0.10.0}/app/services/reference_presentation.py +0 -0
  247. {metalist-0.9.0 → metalist-0.10.0}/app/services/reminders.py +0 -0
  248. {metalist-0.9.0 → metalist-0.10.0}/app/services/remote_image_proxy.py +0 -0
  249. {metalist-0.9.0 → metalist-0.10.0}/app/services/request_recovery.py +0 -0
  250. {metalist-0.9.0 → metalist-0.10.0}/app/services/resource_limits.py +0 -0
  251. {metalist-0.9.0 → metalist-0.10.0}/app/services/root_sorting.py +0 -0
  252. {metalist-0.9.0 → metalist-0.10.0}/app/services/runtime_generation.py +0 -0
  253. {metalist-0.9.0 → metalist-0.10.0}/app/services/runtime_hardening.py +0 -0
  254. {metalist-0.9.0 → metalist-0.10.0}/app/services/runtime_lock.py +0 -0
  255. {metalist-0.9.0 → metalist-0.10.0}/app/services/search_history.py +0 -0
  256. {metalist-0.9.0 → metalist-0.10.0}/app/services/search_history_storage.py +0 -0
  257. {metalist-0.9.0 → metalist-0.10.0}/app/services/search_index.py +0 -0
  258. {metalist-0.9.0 → metalist-0.10.0}/app/services/search_query.py +0 -0
  259. {metalist-0.9.0 → metalist-0.10.0}/app/services/search_text.py +0 -0
  260. {metalist-0.9.0 → metalist-0.10.0}/app/services/selected_text_tag.py +0 -0
  261. {metalist-0.9.0 → metalist-0.10.0}/app/services/self_update.py +0 -0
  262. {metalist-0.9.0 → metalist-0.10.0}/app/services/session_timeout_service.py +0 -0
  263. {metalist-0.9.0 → metalist-0.10.0}/app/services/shell_session_service.py +0 -0
  264. {metalist-0.9.0 → metalist-0.10.0}/app/services/snapshot.py +0 -0
  265. {metalist-0.9.0 → metalist-0.10.0}/app/services/store.py +0 -0
  266. {metalist-0.9.0 → metalist-0.10.0}/app/services/structured_note_renderers.py +0 -0
  267. {metalist-0.9.0 → metalist-0.10.0}/app/services/sync.py +0 -0
  268. {metalist-0.9.0 → metalist-0.10.0}/app/services/sync_state.py +0 -0
  269. {metalist-0.9.0 → metalist-0.10.0}/app/services/tab_state.py +0 -0
  270. {metalist-0.9.0 → metalist-0.10.0}/app/services/tag_case.py +0 -0
  271. {metalist-0.9.0 → metalist-0.10.0}/app/services/tag_ontology.py +0 -0
  272. {metalist-0.9.0 → metalist-0.10.0}/app/services/tag_rename.py +0 -0
  273. {metalist-0.9.0 → metalist-0.10.0}/app/services/tag_suggestions.py +0 -0
  274. {metalist-0.9.0 → metalist-0.10.0}/app/services/tag_term_matching.py +0 -0
  275. {metalist-0.9.0 → metalist-0.10.0}/app/services/test_reset.py +0 -0
  276. {metalist-0.9.0 → metalist-0.10.0}/app/services/tokens.py +0 -0
  277. {metalist-0.9.0 → metalist-0.10.0}/app/services/update_backups.py +0 -0
  278. {metalist-0.9.0 → metalist-0.10.0}/app/services/update_installer.py +0 -0
  279. {metalist-0.9.0 → metalist-0.10.0}/app/services/update_preflight.py +0 -0
  280. {metalist-0.9.0 → metalist-0.10.0}/app/services/view_cache.py +0 -0
  281. {metalist-0.9.0 → metalist-0.10.0}/app/services/view_diff.py +0 -0
  282. {metalist-0.9.0 → metalist-0.10.0}/app/services/view_state.py +0 -0
  283. {metalist-0.9.0 → metalist-0.10.0}/app/services/windows_process_control.py +0 -0
  284. {metalist-0.9.0 → metalist-0.10.0}/app/startup_environment.py +0 -0
  285. {metalist-0.9.0 → metalist-0.10.0}/app/startup_js_sanity.py +0 -0
  286. {metalist-0.9.0 → metalist-0.10.0}/app/startup_sanity.py +0 -0
  287. {metalist-0.9.0 → metalist-0.10.0}/app/startup_sanity_config.py +0 -0
  288. {metalist-0.9.0 → metalist-0.10.0}/app/static/css/floating-notes.css +0 -0
  289. {metalist-0.9.0 → metalist-0.10.0}/app/static/css/forms.css +0 -0
  290. {metalist-0.9.0 → metalist-0.10.0}/app/static/img/favicon.ico +0 -0
  291. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/locked.js +0 -0
  292. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/main.js +0 -0
  293. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/activity-tracker.js +0 -0
  294. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/agent-menu-actions.js +0 -0
  295. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/agent-prompt-service.js +0 -0
  296. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/agent-retrieval-settings.js +0 -0
  297. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/agent-web-settings.js +0 -0
  298. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/ai-agent-debug-view.js +0 -0
  299. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/ai-chat-api.js +0 -0
  300. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/ai-chat-message-placement.js +0 -0
  301. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/ai-chat-panel-controller.js +0 -0
  302. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/ai-chat-panel-service.js +0 -0
  303. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/ai-thinking-level-service.js +0 -0
  304. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/bulk-elapsed-clock.js +0 -0
  305. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/bulk-proposal-ui.js +0 -0
  306. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/bulk-scope-question.js +0 -0
  307. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/cloud-privacy-policy.js +0 -0
  308. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/proposal-menu.js +0 -0
  309. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/summary-batch-preview.js +0 -0
  310. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/api-error-diagnostics.js +0 -0
  311. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/api-failure-classification-service.js +0 -0
  312. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/app-update-service.js +0 -0
  313. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/application-state.js +0 -0
  314. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/async-result.js +0 -0
  315. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/auth.js +0 -0
  316. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/client-state-api.js +0 -0
  317. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/client-state-migration.js +0 -0
  318. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/activation-auth-policy.js +0 -0
  319. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/backup-command-availability.js +0 -0
  320. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
  321. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/preferences-store.js +0 -0
  322. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/usage-store.js +0 -0
  323. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/connectivity-monitor.js +0 -0
  324. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/context-menu/context-menu-target-service.js +0 -0
  325. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
  326. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/disconnect-editing-state-service.js +0 -0
  327. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/dom-utils-fixed.js +0 -0
  328. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/dom-utils.js +0 -0
  329. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/editor-commands.js +0 -0
  330. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/elapsed-time.js +0 -0
  331. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/error-handler.js +0 -0
  332. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/error-overlay.js +0 -0
  333. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/location-flags.js +0 -0
  334. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/login-namespace-picker.js +0 -0
  335. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/maintenance.js +0 -0
  336. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/agent-prompt-editor-modal.js +0 -0
  337. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/ai-agent-settings-modal.js +0 -0
  338. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
  339. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/backup-restore-modal.js +0 -0
  340. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/backup-result-modal.js +0 -0
  341. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
  342. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/backup-settings-modal.js +0 -0
  343. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/base-modal.js +0 -0
  344. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/confirmation-modal.js +0 -0
  345. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/delete-namespace-modal.js +0 -0
  346. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/delete-namespace-validation.js +0 -0
  347. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/help-modal.js +0 -0
  348. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/namespace-loading-page.js +0 -0
  349. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/namespace-modals.js +0 -0
  350. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/namespace-ports-profile.js +0 -0
  351. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/namespace-rename-validation.js +0 -0
  352. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/ontology-modal.js +0 -0
  353. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/password-modal.js +0 -0
  354. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/prioritize-modal.js +0 -0
  355. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/random-password-modal.js +0 -0
  356. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/reminder-modal.js +0 -0
  357. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
  358. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/search-suggestion-statistics-modal.js +0 -0
  359. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
  360. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/version-info-modal.js +0 -0
  361. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/actions/content-actions.js +0 -0
  362. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
  363. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/actions/note-actions.js +0 -0
  364. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
  365. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
  366. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/actions/ui-actions.js +0 -0
  367. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
  368. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
  369. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
  370. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/mode-context.js +0 -0
  371. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/mode-logger.js +0 -0
  372. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
  373. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/add-style-service.js +0 -0
  374. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
  375. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/backlinks-navigation-service.js +0 -0
  376. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
  377. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
  378. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
  379. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
  380. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -0
  381. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +0 -0
  382. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/deselect-selection-state-service.js +0 -0
  383. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
  384. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
  385. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
  386. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/file-drop-policy-service.js +0 -0
  387. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
  388. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
  389. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
  390. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/floating-note-dom.js +0 -0
  391. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/floating-note-layout.js +0 -0
  392. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/footnote-navigation-service.js +0 -0
  393. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/history-selection-policy-service.js +0 -0
  394. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/history-shortcut-policy-service.js +0 -0
  395. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +0 -0
  396. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +0 -0
  397. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
  398. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
  399. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/link-title-prefetch-service.js +0 -0
  400. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
  401. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/mermaid-render-service.js +0 -0
  402. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
  403. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
  404. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
  405. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
  406. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-delete-shortcut-policy-service.js +0 -0
  407. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
  408. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
  409. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
  410. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-timestamp-hover-service.js +0 -0
  411. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
  412. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
  413. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/reference-source-navigation-service.js +0 -0
  414. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/remote-image-proxy-service.js +0 -0
  415. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/remove-formatting-service.js +0 -0
  416. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
  417. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
  418. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
  419. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
  420. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
  421. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
  422. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
  423. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
  424. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
  425. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
  426. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
  427. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-suggestion-statistics-service.js +0 -0
  428. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-suggestion-window-labels-service.js +0 -0
  429. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-suggestion-windows-service.js +0 -0
  430. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +0 -0
  431. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
  432. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/selected-formatting-removal-service.js +0 -0
  433. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/selected-text-tag-service.js +0 -0
  434. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
  435. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
  436. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tab-drag-service.js +0 -0
  437. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
  438. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -0
  439. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tag-bar-service.js +0 -0
  440. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tag-input-selection-service.js +0 -0
  441. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tag-proposal-service.js +0 -0
  442. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
  443. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/untagged-view-indicator-service.js +0 -0
  444. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
  445. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/note-html-sanitizer.js +0 -0
  446. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/password-generator.js +0 -0
  447. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/password-operation-response.js +0 -0
  448. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/password-policy.js +0 -0
  449. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/password-strength.js +0 -0
  450. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/reminder-store.js +0 -0
  451. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/reminder-surface-service.js +0 -0
  452. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/selection-markers.js +0 -0
  453. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/session-auth.js +0 -0
  454. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/tag-token.js +0 -0
  455. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/uuid.js +0 -0
  456. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/namespace-deleted.js +0 -0
  457. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/namespace-renamed.js +0 -0
  458. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/dompurify-3.4.13.LICENSE.txt +0 -0
  459. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/dompurify-3.4.13.min.js +0 -0
  460. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/markdown-it-14.3.2.LICENSE.txt +0 -0
  461. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/markdown-it-14.3.2.min.js +0 -0
  462. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/mermaid-11.16.1.LICENSE.txt +0 -0
  463. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/mermaid-11.16.1.min.js +0 -0
  464. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/zxcvbn-4.4.2.LICENSE.txt +0 -0
  465. {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/zxcvbn-4.4.2.js +0 -0
  466. {metalist-0.9.0 → metalist-0.10.0}/app/static/note-html-policy.json +0 -0
  467. {metalist-0.9.0 → metalist-0.10.0}/app/static/video/login-startup.mp4 +0 -0
  468. {metalist-0.9.0 → metalist-0.10.0}/app/templates/base.html +0 -0
  469. {metalist-0.9.0 → metalist-0.10.0}/app/templates/index.html +0 -0
  470. {metalist-0.9.0 → metalist-0.10.0}/app/templates/locked.html +0 -0
  471. {metalist-0.9.0 → metalist-0.10.0}/app/templates/maintenance.html +0 -0
  472. {metalist-0.9.0 → metalist-0.10.0}/app/templates/namespace_deleted.html +0 -0
  473. {metalist-0.9.0 → metalist-0.10.0}/app/templates/namespace_renamed.html +0 -0
  474. {metalist-0.9.0 → metalist-0.10.0}/app/templates/notes_list.html +0 -0
  475. {metalist-0.9.0 → metalist-0.10.0}/app/upload_limits.py +0 -0
  476. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/__init__.py +0 -0
  477. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/add_selected_text_tag.py +0 -0
  478. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/alphabetize_root_notes.py +0 -0
  479. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/base.py +0 -0
  480. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/bulk_tag_proposals.py +0 -0
  481. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/collapse.py +0 -0
  482. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/copy_note.py +0 -0
  483. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/create_child.py +0 -0
  484. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/create_note.py +0 -0
  485. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/create_sibling.py +0 -0
  486. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/delete_subtree.py +0 -0
  487. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/delete_tag.py +0 -0
  488. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/expand.py +0 -0
  489. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/indent.py +0 -0
  490. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/lock.py +0 -0
  491. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/move.py +0 -0
  492. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/move_to_top.py +0 -0
  493. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/outdent.py +0 -0
  494. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/paste_child.py +0 -0
  495. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/paste_sibling.py +0 -0
  496. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/prefetch_link_titles.py +0 -0
  497. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/prioritize.py +0 -0
  498. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/redo.py +0 -0
  499. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/rename_tag.py +0 -0
  500. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/reset_updated_at.py +0 -0
  501. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/run_shell.py +0 -0
  502. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/search_comment_autofill.py +0 -0
  503. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/search_context_tags.py +0 -0
  504. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/set_collapse_bulk.py +0 -0
  505. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/set_collapse_in_context.py +0 -0
  506. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/split_note.py +0 -0
  507. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/tag_proposals.py +0 -0
  508. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/toggle_reference_mode.py +0 -0
  509. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/toggle_todo_done.py +0 -0
  510. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/undo.py +0 -0
  511. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/unformat_content.py +0 -0
  512. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/update_content.py +0 -0
  513. {metalist-0.9.0 → metalist-0.10.0}/app/usecases/view.py +0 -0
  514. {metalist-0.9.0 → metalist-0.10.0}/app/utils/__init__.py +0 -0
  515. {metalist-0.9.0 → metalist-0.10.0}/app/utils/encryption.py +0 -0
  516. {metalist-0.9.0 → metalist-0.10.0}/app/utils/text_utils.py +0 -0
  517. {metalist-0.9.0 → metalist-0.10.0}/convert-from-legacy.py +0 -0
  518. {metalist-0.9.0 → metalist-0.10.0}/main.py +0 -0
  519. {metalist-0.9.0 → metalist-0.10.0}/metalist.egg-info/dependency_links.txt +0 -0
  520. {metalist-0.9.0 → metalist-0.10.0}/metalist.egg-info/entry_points.txt +0 -0
  521. {metalist-0.9.0 → metalist-0.10.0}/metalist.egg-info/requires.txt +0 -0
  522. {metalist-0.9.0 → metalist-0.10.0}/metalist.egg-info/top_level.txt +0 -0
  523. {metalist-0.9.0 → metalist-0.10.0}/pyproject.toml +0 -0
  524. {metalist-0.9.0 → metalist-0.10.0}/scripts/generate-lan-cert.sh +0 -0
  525. {metalist-0.9.0 → metalist-0.10.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metalist
3
- Version: 0.9.0
3
+ Version: 0.10.0
4
4
  Summary: A minimalist single-user note-taking app with SSR, fast in-memory tree operations, and efficient sync.
5
5
  Project-URL: Homepage, https://github.com/evolvingstuff/metalist
6
6
  Project-URL: Repository, https://github.com/evolvingstuff/metalist
@@ -49,6 +49,14 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
49
49
  - Multi-tab search contexts with server-persisted scroll/search state (survives browser restarts)
50
50
  - Manual namespace backups/restores to a user-selected backup folder with retention controls
51
51
 
52
+ ## Changes in 0.10.0
53
+
54
+ - Excalidraw diagrams: add one from the note's right-click menu or the command palette, and double-click a diagram to edit it full screen. Diagrams autosave, show light and dark previews, resize like images, and each editing session is one undo step. The editor is bundled locally and needs no Node server.
55
+ - The dark theme uses neutral greys with a brighter accent, the light theme header is darker, and Note Layout & Appearance adds note corner and tag style settings.
56
+ - References to a note that starts with an image or diagram show its thumbnail. Collapsed notes, reference titles, and backlink previews show only the first line.
57
+ - Fixed a command palette crash on queries with no matches.
58
+ - The database schema moves to version 10. Password-protected namespaces migrate after unlock, with the usual automatic backup first.
59
+
52
60
  ## Changes in 0.9.0
53
61
 
54
62
  - AI chat can browse the web in disabled, contextual, or unrestricted mode, fetch multiple pages concurrently, and cite the specific pages used as evidence.
@@ -12,6 +12,14 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
12
12
  - Multi-tab search contexts with server-persisted scroll/search state (survives browser restarts)
13
13
  - Manual namespace backups/restores to a user-selected backup folder with retention controls
14
14
 
15
+ ## Changes in 0.10.0
16
+
17
+ - Excalidraw diagrams: add one from the note's right-click menu or the command palette, and double-click a diagram to edit it full screen. Diagrams autosave, show light and dark previews, resize like images, and each editing session is one undo step. The editor is bundled locally and needs no Node server.
18
+ - The dark theme uses neutral greys with a brighter accent, the light theme header is darker, and Note Layout & Appearance adds note corner and tag style settings.
19
+ - References to a note that starts with an image or diagram show its thumbnail. Collapsed notes, reference titles, and backlink previews show only the first line.
20
+ - Fixed a command palette crash on queries with no matches.
21
+ - The database schema moves to version 10. Password-protected namespaces migrate after unlock, with the usual automatic backup first.
22
+
15
23
  ## Changes in 0.9.0
16
24
 
17
25
  - AI chat can browse the web in disabled, contextual, or unrestricted mode, fetch multiple pages concurrently, and cite the specific pages used as evidence.
@@ -206,6 +206,19 @@ class ResizeNoteImageRequest(TypedDict):
206
206
  undoContext: UndoContext
207
207
  viewport: Viewport
208
208
 
209
+ @with_config(ConfigDict(strict=True))
210
+ class FinishFileEditSessionRequest(TypedDict):
211
+ clientId: Identifier
212
+ hostNoteId: Identifier
213
+ contentRevision: Annotated[int, Field(ge=1)]
214
+ isNewDiagram: bool
215
+ undoContext: UndoContext
216
+ viewport: Viewport
217
+
218
+ @with_config(ConfigDict(strict=True))
219
+ class DiscardFileEditSessionRequest(TypedDict):
220
+ contentRevision: Annotated[int, Field(ge=1)]
221
+
209
222
  @with_config(ConfigDict(strict=True))
210
223
  class RunShellEndpointRequest(TypedDict):
211
224
  timeoutSeconds: Annotated[int, Field(ge=0, le=SHELL_SECONDS)]
@@ -0,0 +1,244 @@
1
+ from __future__ import annotations
2
+
3
+ from io import BytesIO
4
+ from typing import Annotated
5
+ from urllib.parse import quote
6
+
7
+ from fastapi import APIRouter, File, Form, HTTPException, Request, UploadFile
8
+ from fastapi.responses import Response, StreamingResponse
9
+ from pydantic import BaseModel
10
+
11
+ from app.api.note_requests import DiscardFileEditSessionRequest, FinishFileEditSessionRequest
12
+ from app.api.transactions import transactional_route
13
+ from app.api.upload_limits import read_attachment
14
+ from app.api.request_auth import require_request_auth_token
15
+ from app.services.file_registry import file_registry
16
+ from app.services.note_store import store as note_store
17
+ from app.db.file_schema import FILE_PREVIEW_VARIANTS
18
+ from app.services.file_storage import (
19
+ create_file,
20
+ download_file,
21
+ get_file_preview,
22
+ replace_file_content,
23
+ store_file_previews,
24
+ trim_unused_files,
25
+ )
26
+ from app.usecases.file_edit_session import (
27
+ CmdDiscardFileEditSession,
28
+ CmdFinishFileEditSession,
29
+ CmdStartFileEditSession,
30
+ )
31
+
32
+ router = APIRouter(prefix="/files", tags=["files"])
33
+
34
+ FILE_REVISION_HEADER = "X-MetaList-File-Revision"
35
+
36
+
37
+ class UploadedFileResponse(BaseModel):
38
+ file_id: str
39
+ reference_token: str
40
+ title: str
41
+ original_filename: str
42
+ mime_type: str
43
+ size_bytes: int
44
+ thumbnail_kind: str
45
+
46
+
47
+ class FileContentUpdatedResponse(BaseModel):
48
+ file_id: str
49
+ content_revision: int
50
+ size_bytes: int
51
+
52
+
53
+ class FilePreviewsStoredResponse(BaseModel):
54
+ file_id: str
55
+ content_revision: int
56
+
57
+
58
+ class TrimUnusedFilesResponse(BaseModel):
59
+ deleted_count: int
60
+ deleted_file_ids: list[str]
61
+
62
+
63
+ def _require_bearer_token(request: Request) -> str:
64
+ return require_request_auth_token(request)
65
+
66
+
67
+ def _require_known_file(file_id: str) -> None:
68
+ if not file_registry.has_file(file_id):
69
+ raise HTTPException(status_code=404, detail=f"File not found: {file_id}")
70
+
71
+
72
+ @router.post("/upload", response_model=UploadedFileResponse)
73
+ @transactional_route
74
+ async def upload_file_endpoint(
75
+ request: Request,
76
+ file: Annotated[UploadFile, File()],
77
+ ):
78
+ token = _require_bearer_token(request)
79
+ if not isinstance(file.filename, str) or file.filename == "":
80
+ raise HTTPException(status_code=400, detail="Uploaded file must include a filename")
81
+ if not isinstance(file.content_type, str) or file.content_type == "":
82
+ raise HTTPException(status_code=400, detail="Uploaded file must include a non-empty MIME type")
83
+
84
+ content_bytes = await read_attachment(file)
85
+
86
+ record = create_file(
87
+ original_filename=file.filename,
88
+ mime_type=file.content_type,
89
+ content_bytes=content_bytes,
90
+ token=token,
91
+ )
92
+ return UploadedFileResponse(
93
+ file_id=record.id,
94
+ reference_token=f"![[{record.id}]]",
95
+ title=record.title,
96
+ original_filename=record.original_filename,
97
+ mime_type=record.mime_type,
98
+ size_bytes=record.size_bytes,
99
+ thumbnail_kind=record.thumbnail_kind,
100
+ )
101
+
102
+
103
+ @router.get("/{file_id}/download")
104
+ def download_file_endpoint(request: Request, file_id: str):
105
+ token = _require_bearer_token(request)
106
+ if not file_registry.has_file(file_id):
107
+ raise HTTPException(status_code=404, detail=f"File not found: {file_id}")
108
+
109
+ downloaded = download_file(file_id, token)
110
+ quoted_filename = quote(downloaded.record.original_filename, safe="")
111
+ return StreamingResponse(
112
+ BytesIO(downloaded.content_bytes),
113
+ media_type=downloaded.record.mime_type,
114
+ headers={
115
+ "Content-Disposition": f"attachment; filename*=UTF-8''{quoted_filename}",
116
+ FILE_REVISION_HEADER: str(downloaded.record.content_revision),
117
+ },
118
+ )
119
+
120
+
121
+ @router.put("/{file_id}/content", response_model=FileContentUpdatedResponse)
122
+ @transactional_route
123
+ async def replace_file_content_endpoint(
124
+ request: Request,
125
+ file_id: str,
126
+ file: Annotated[UploadFile, File()],
127
+ expected_revision: Annotated[int, Form()],
128
+ ):
129
+ token = _require_bearer_token(request)
130
+ _require_known_file(file_id)
131
+ content_bytes = await read_attachment(file)
132
+ record = replace_file_content(
133
+ file_id=file_id,
134
+ content_bytes=content_bytes,
135
+ expected_revision=expected_revision,
136
+ token=token,
137
+ )
138
+ return FileContentUpdatedResponse(
139
+ file_id=record.id,
140
+ content_revision=record.content_revision,
141
+ size_bytes=record.size_bytes,
142
+ )
143
+
144
+
145
+ @router.put("/{file_id}/previews", response_model=FilePreviewsStoredResponse)
146
+ @transactional_route
147
+ async def store_file_previews_endpoint(
148
+ request: Request,
149
+ file_id: str,
150
+ light: Annotated[UploadFile, File()],
151
+ dark: Annotated[UploadFile, File()],
152
+ content_revision: Annotated[int, Form()],
153
+ ):
154
+ token = _require_bearer_token(request)
155
+ _require_known_file(file_id)
156
+ previews_by_variant = {
157
+ "light": await read_attachment(light),
158
+ "dark": await read_attachment(dark),
159
+ }
160
+ record = store_file_previews(
161
+ file_id=file_id,
162
+ previews_by_variant=previews_by_variant,
163
+ content_revision=content_revision,
164
+ token=token,
165
+ )
166
+ return FilePreviewsStoredResponse(file_id=record.id, content_revision=record.content_revision)
167
+
168
+
169
+ @router.get("/{file_id}/previews/{variant}")
170
+ def download_file_preview_endpoint(request: Request, file_id: str, variant: str):
171
+ token = _require_bearer_token(request)
172
+ _require_known_file(file_id)
173
+ if variant not in FILE_PREVIEW_VARIANTS:
174
+ raise HTTPException(status_code=404, detail=f"Unknown preview variant: {variant}")
175
+ preview = get_file_preview(file_id=file_id, variant=variant, token=token)
176
+ return Response(
177
+ content=preview.content_bytes,
178
+ media_type=preview.mime_type,
179
+ headers={FILE_REVISION_HEADER: str(preview.content_revision)},
180
+ )
181
+
182
+
183
+ # A diagram editing session: opened with the editor, finished by Done (one undo step), or discarded.
184
+ @router.post("/{file_id}/edit-sessions")
185
+ @transactional_route
186
+ def start_file_edit_session_endpoint(request: Request, file_id: str) -> dict[str, object]:
187
+ token = _require_bearer_token(request)
188
+ _require_known_file(file_id)
189
+ return CmdStartFileEditSession(file_id=file_id, token=token).execute()
190
+
191
+
192
+ @router.post("/{file_id}/edit-sessions/{session_id}/finish")
193
+ @transactional_route
194
+ def finish_file_edit_session_endpoint(
195
+ request: Request,
196
+ file_id: str,
197
+ session_id: str,
198
+ body: FinishFileEditSessionRequest,
199
+ ) -> dict[str, object]:
200
+ token = _require_bearer_token(request)
201
+ _require_known_file(file_id)
202
+ host_note_id = body["hostNoteId"]
203
+ if not note_store.has_note(host_note_id):
204
+ raise HTTPException(status_code=404, detail=f"Note not found: {host_note_id}")
205
+ return CmdFinishFileEditSession(
206
+ file_id=file_id,
207
+ session_id=session_id,
208
+ host_note_id=host_note_id,
209
+ content_revision=body["contentRevision"],
210
+ is_new_diagram=body["isNewDiagram"],
211
+ token=token,
212
+ client_id=body["clientId"],
213
+ undo_context=body["undoContext"],
214
+ viewport=body["viewport"],
215
+ ).execute()
216
+
217
+
218
+ @router.post("/{file_id}/edit-sessions/{session_id}/discard")
219
+ @transactional_route
220
+ def discard_file_edit_session_endpoint(
221
+ request: Request,
222
+ file_id: str,
223
+ session_id: str,
224
+ body: DiscardFileEditSessionRequest,
225
+ ) -> dict[str, object]:
226
+ token = _require_bearer_token(request)
227
+ _require_known_file(file_id)
228
+ return CmdDiscardFileEditSession(
229
+ file_id=file_id,
230
+ session_id=session_id,
231
+ content_revision=body["contentRevision"],
232
+ token=token,
233
+ ).execute()
234
+
235
+
236
+ @router.post("/trim-unused", response_model=TrimUnusedFilesResponse)
237
+ @transactional_route
238
+ def trim_unused_files_endpoint(request: Request):
239
+ _require_bearer_token(request)
240
+ result = trim_unused_files()
241
+ return TrimUnusedFilesResponse(
242
+ deleted_count=result.deleted_count,
243
+ deleted_file_ids=result.deleted_file_ids,
244
+ )
@@ -23,6 +23,18 @@ async def read_attachment(file: UploadFile) -> bytes:
23
23
  await file.close()
24
24
 
25
25
 
26
+ def _is_upload_request(scope) -> bool:
27
+ path = scope['path']
28
+ if path.endswith('/files/upload'):
29
+ return True
30
+ # In-place updates of MetaList-edited files (Excalidraw scenes and their previews) are uploads too.
31
+ if '/files/' not in path:
32
+ return False
33
+ if not path.endswith('/content') and not path.endswith('/previews'):
34
+ return False
35
+ return scope['method'] == 'PUT'
36
+
37
+
26
38
  class UploadLimitMiddleware:
27
39
  def __init__(self, app):
28
40
  self.app = app
@@ -32,7 +44,7 @@ class UploadLimitMiddleware:
32
44
  if scope['type'] != 'http':
33
45
  await self.app(scope, receive, send)
34
46
  return
35
- is_upload = scope['path'].endswith('/files/upload')
47
+ is_upload = _is_upload_request(scope)
36
48
  is_download = '/files/' in scope['path'] and scope['path'].endswith('/download')
37
49
  if not (is_upload or is_download):
38
50
  await self.app(scope, receive, send)
@@ -0,0 +1,50 @@
1
+ """Migration 9→10: let MetaList edit some attachments in place (Excalidraw diagrams).
2
+
3
+ The sibling files database gains `files.content_revision` and the encrypted `file_previews`
4
+ table. Opening a files database also adds them (see `initialize_file_schema`), so this step is
5
+ idempotent; it exists so the upgrade is recorded in the namespace version like every other
6
+ schema change, and so the encryption audit can require the new schema from version 10 on.
7
+ """
8
+
9
+ from contextlib import closing
10
+ from pathlib import Path
11
+ import sqlite3
12
+
13
+ from app.db.file_schema import FILE_PREVIEWS_TABLE, FILES_TABLE, initialize_file_schema
14
+
15
+ # Same shared in-memory database FileSession uses when the notes database is in memory.
16
+ _FILE_MEMORY_URI = "file:metalist_files_memory?mode=memory&cache=shared"
17
+
18
+
19
+ def _upgrade(files: sqlite3.Connection) -> None:
20
+ initialize_file_schema(files)
21
+ files.commit()
22
+ columns = {row[1] for row in files.execute(f"PRAGMA table_info({FILES_TABLE})").fetchall()}
23
+ if "content_revision" not in columns:
24
+ raise RuntimeError("Database migration 9→10 did not add files.content_revision")
25
+ preview_table = files.execute(
26
+ "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?", (FILE_PREVIEWS_TABLE,)
27
+ ).fetchone()
28
+ if preview_table is None:
29
+ raise RuntimeError(f"Database migration 9→10 did not create {FILE_PREVIEWS_TABLE}")
30
+
31
+
32
+ def add_editable_file_schema(
33
+ *,
34
+ connection: sqlite3.Connection,
35
+ encryption_enabled: bool, # noqa: ARG001
36
+ encryption_service: object, # noqa: ARG001
37
+ ) -> int:
38
+ database = connection.execute("PRAGMA database_list").fetchone()[2]
39
+ if not database:
40
+ with closing(sqlite3.connect(_FILE_MEMORY_URI, uri=True)) as files:
41
+ _upgrade(files)
42
+ return 0
43
+ notes = Path(database)
44
+ target = notes.with_name(f"{notes.stem}.files{notes.suffix}")
45
+ # A namespace without attachments gets the current schema when its files database is created.
46
+ if not target.exists():
47
+ return 0
48
+ with closing(sqlite3.connect(target)) as files:
49
+ _upgrade(files)
50
+ return 0
@@ -0,0 +1,58 @@
1
+ """SQLite schema helpers for encrypted file storage."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from sqlite3 import Connection
6
+
7
+ FILES_TABLE = "files"
8
+ FILE_PREVIEWS_TABLE = "file_previews"
9
+ FILE_PREVIEW_VARIANTS = ("light", "dark")
10
+
11
+ _CREATE_FILES_TABLE = f"""
12
+ CREATE TABLE IF NOT EXISTS {FILES_TABLE} (
13
+ id TEXT PRIMARY KEY,
14
+ title TEXT NOT NULL,
15
+ title_encryption_nonce BLOB,
16
+ title_encryption_tag BLOB,
17
+ metadata_json TEXT NOT NULL,
18
+ metadata_encryption_nonce BLOB,
19
+ metadata_encryption_tag BLOB,
20
+ blob_data BLOB NOT NULL,
21
+ blob_encryption_nonce BLOB,
22
+ blob_encryption_tag BLOB,
23
+ created_at TEXT NOT NULL,
24
+ updated_at TEXT NOT NULL,
25
+ content_revision INTEGER NOT NULL DEFAULT 1
26
+ );
27
+ """
28
+
29
+ # Rendered previews for files edited inside MetaList (Excalidraw diagrams).
30
+ # One row per theme variant; content_revision records which file revision was rendered.
31
+ _CREATE_FILE_PREVIEWS_TABLE = f"""
32
+ CREATE TABLE IF NOT EXISTS {FILE_PREVIEWS_TABLE} (
33
+ file_id TEXT NOT NULL REFERENCES {FILES_TABLE}(id) ON DELETE CASCADE,
34
+ variant TEXT NOT NULL CHECK (variant IN ('light', 'dark')),
35
+ mime_type TEXT NOT NULL,
36
+ preview_data BLOB NOT NULL,
37
+ preview_encryption_nonce BLOB,
38
+ preview_encryption_tag BLOB,
39
+ content_revision INTEGER NOT NULL,
40
+ updated_at TEXT NOT NULL,
41
+ PRIMARY KEY (file_id, variant)
42
+ );
43
+ """
44
+
45
+
46
+ def _add_content_revision_column_if_missing(connection: Connection) -> None:
47
+ """Upgrade live file databases created before in-place file updates existed."""
48
+ column_names = {row[1] for row in connection.execute(f"PRAGMA table_info({FILES_TABLE})").fetchall()}
49
+ assert "id" in column_names, "files table must exist before its columns are upgraded"
50
+ if "content_revision" in column_names:
51
+ return
52
+ connection.execute(f"ALTER TABLE {FILES_TABLE} ADD COLUMN content_revision INTEGER NOT NULL DEFAULT 1")
53
+
54
+
55
+ def initialize_file_schema(connection: Connection) -> None:
56
+ connection.execute(_CREATE_FILES_TABLE)
57
+ _add_content_revision_column_if_missing(connection)
58
+ connection.execute(_CREATE_FILE_PREVIEWS_TABLE)
@@ -6,7 +6,7 @@ import sqlite3
6
6
  from datetime import datetime
7
7
  from typing import Iterable, Optional
8
8
 
9
- from app.db.file_schema import FILES_TABLE
9
+ from app.db.file_schema import FILE_PREVIEW_VARIANTS, FILE_PREVIEWS_TABLE, FILES_TABLE
10
10
 
11
11
 
12
12
  def _serialize_datetime(value: datetime) -> str:
@@ -24,6 +24,10 @@ def _deserialize_row(row: sqlite3.Row) -> dict[str, object]:
24
24
  raise TypeError(f"files.{field} must be a string | file_id={file_id} value={raw!r}")
25
25
  return datetime.fromisoformat(raw)
26
26
 
27
+ content_revision = row["content_revision"]
28
+ if not isinstance(content_revision, int) or content_revision < 1:
29
+ raise TypeError(f"files.content_revision must be a positive integer | file_id={file_id} value={content_revision!r}")
30
+
27
31
  return {
28
32
  "id": file_id,
29
33
  "title": row["title"],
@@ -37,6 +41,7 @@ def _deserialize_row(row: sqlite3.Row) -> dict[str, object]:
37
41
  "blob_encryption_tag": row["blob_encryption_tag"],
38
42
  "created_at": _parse_datetime("created_at"),
39
43
  "updated_at": _parse_datetime("updated_at"),
44
+ "content_revision": content_revision,
40
45
  }
41
46
 
42
47
 
@@ -90,7 +95,7 @@ def insert_file(
90
95
  )
91
96
 
92
97
 
93
- _METADATA_COLUMNS = "id, title, title_encryption_nonce, title_encryption_tag, metadata_json, metadata_encryption_nonce, metadata_encryption_tag, created_at, updated_at, X'' AS blob_data, blob_encryption_nonce, blob_encryption_tag"
98
+ _METADATA_COLUMNS = "id, title, title_encryption_nonce, title_encryption_tag, metadata_json, metadata_encryption_nonce, metadata_encryption_tag, created_at, updated_at, content_revision, X'' AS blob_data, blob_encryption_nonce, blob_encryption_tag"
94
99
 
95
100
 
96
101
  class AttachmentSizeExceeded(ValueError):
@@ -200,3 +205,157 @@ def delete_files(connection: sqlite3.Connection, file_ids: Iterable[str]) -> int
200
205
  tuple(identifiers),
201
206
  )
202
207
  return int(cursor.rowcount)
208
+
209
+
210
+ def replace_file_content_fields(
211
+ connection: sqlite3.Connection,
212
+ *,
213
+ file_id: str,
214
+ metadata_json: str,
215
+ metadata_encryption_nonce: Optional[bytes],
216
+ metadata_encryption_tag: Optional[bytes],
217
+ blob_data: bytes,
218
+ blob_encryption_nonce: Optional[bytes],
219
+ blob_encryption_tag: Optional[bytes],
220
+ expected_revision: int,
221
+ updated_at: datetime,
222
+ ) -> int:
223
+ """Replace a file's content when it is still at expected_revision; returns the updated row count."""
224
+ if not isinstance(expected_revision, int) or expected_revision < 1:
225
+ raise TypeError(f"expected_revision must be a positive integer, got {expected_revision!r}")
226
+ cursor = connection.execute(
227
+ f"""
228
+ UPDATE {FILES_TABLE}
229
+ SET
230
+ metadata_json = ?,
231
+ metadata_encryption_nonce = ?,
232
+ metadata_encryption_tag = ?,
233
+ blob_data = ?,
234
+ blob_encryption_nonce = ?,
235
+ blob_encryption_tag = ?,
236
+ updated_at = ?,
237
+ content_revision = content_revision + 1
238
+ WHERE id = ? AND content_revision = ?
239
+ """,
240
+ (
241
+ metadata_json,
242
+ metadata_encryption_nonce,
243
+ metadata_encryption_tag,
244
+ blob_data,
245
+ blob_encryption_nonce,
246
+ blob_encryption_tag,
247
+ _serialize_datetime(updated_at),
248
+ file_id,
249
+ expected_revision,
250
+ ),
251
+ )
252
+ return int(cursor.rowcount)
253
+
254
+
255
+ def _require_preview_variant(variant: str) -> None:
256
+ if variant not in FILE_PREVIEW_VARIANTS:
257
+ raise ValueError(f"file preview variant must be one of {FILE_PREVIEW_VARIANTS}, got {variant!r}")
258
+
259
+
260
+ def _deserialize_preview_row(row: sqlite3.Row) -> dict[str, object]:
261
+ file_id = row["file_id"]
262
+ content_revision = row["content_revision"]
263
+ if not isinstance(content_revision, int) or content_revision < 1:
264
+ raise TypeError(f"file_previews.content_revision invalid | file_id={file_id} value={content_revision!r}")
265
+ updated_at = row["updated_at"]
266
+ if not isinstance(updated_at, str):
267
+ raise TypeError(f"file_previews.updated_at must be a string | file_id={file_id}")
268
+ return {
269
+ "file_id": file_id,
270
+ "variant": row["variant"],
271
+ "mime_type": row["mime_type"],
272
+ "preview_data": row["preview_data"],
273
+ "preview_encryption_nonce": row["preview_encryption_nonce"],
274
+ "preview_encryption_tag": row["preview_encryption_tag"],
275
+ "content_revision": content_revision,
276
+ "updated_at": datetime.fromisoformat(updated_at),
277
+ }
278
+
279
+
280
+ def upsert_file_preview(
281
+ connection: sqlite3.Connection,
282
+ *,
283
+ file_id: str,
284
+ variant: str,
285
+ mime_type: str,
286
+ preview_data: bytes,
287
+ preview_encryption_nonce: Optional[bytes],
288
+ preview_encryption_tag: Optional[bytes],
289
+ content_revision: int,
290
+ updated_at: datetime,
291
+ ) -> None:
292
+ _require_preview_variant(variant)
293
+ connection.execute(
294
+ f"""
295
+ INSERT INTO {FILE_PREVIEWS_TABLE} (
296
+ file_id, variant, mime_type, preview_data, preview_encryption_nonce,
297
+ preview_encryption_tag, content_revision, updated_at
298
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
299
+ ON CONFLICT(file_id, variant) DO UPDATE SET
300
+ mime_type = excluded.mime_type,
301
+ preview_data = excluded.preview_data,
302
+ preview_encryption_nonce = excluded.preview_encryption_nonce,
303
+ preview_encryption_tag = excluded.preview_encryption_tag,
304
+ content_revision = excluded.content_revision,
305
+ updated_at = excluded.updated_at
306
+ """,
307
+ (
308
+ file_id,
309
+ variant,
310
+ mime_type,
311
+ preview_data,
312
+ preview_encryption_nonce,
313
+ preview_encryption_tag,
314
+ content_revision,
315
+ _serialize_datetime(updated_at),
316
+ ),
317
+ )
318
+
319
+
320
+ def delete_file_previews(connection: sqlite3.Connection, file_id: str) -> int:
321
+ cursor = connection.execute(f"DELETE FROM {FILE_PREVIEWS_TABLE} WHERE file_id = ?", (file_id,))
322
+ return cursor.rowcount
323
+
324
+
325
+ def fetch_file_preview(connection: sqlite3.Connection, file_id: str, variant: str) -> Optional[dict[str, object]]:
326
+ _require_preview_variant(variant)
327
+ row = connection.execute(
328
+ f"SELECT * FROM {FILE_PREVIEWS_TABLE} WHERE file_id = ? AND variant = ?",
329
+ (file_id, variant),
330
+ ).fetchone()
331
+ if row is None:
332
+ return None
333
+ return _deserialize_preview_row(row)
334
+
335
+
336
+ def fetch_all_file_previews(connection: sqlite3.Connection) -> list[dict[str, object]]:
337
+ rows = connection.execute(f"SELECT * FROM {FILE_PREVIEWS_TABLE} ORDER BY file_id ASC, variant ASC").fetchall()
338
+ return [_deserialize_preview_row(row) for row in rows]
339
+
340
+
341
+ def update_file_preview_storage_fields(
342
+ connection: sqlite3.Connection,
343
+ *,
344
+ file_id: str,
345
+ variant: str,
346
+ preview_data: bytes,
347
+ preview_encryption_nonce: Optional[bytes],
348
+ preview_encryption_tag: Optional[bytes],
349
+ ) -> None:
350
+ """Rewrite preview ciphertext during encryption migrations without changing its revision."""
351
+ _require_preview_variant(variant)
352
+ cursor = connection.execute(
353
+ f"""
354
+ UPDATE {FILE_PREVIEWS_TABLE}
355
+ SET preview_data = ?, preview_encryption_nonce = ?, preview_encryption_tag = ?
356
+ WHERE file_id = ? AND variant = ?
357
+ """,
358
+ (preview_data, preview_encryption_nonce, preview_encryption_tag, file_id, variant),
359
+ )
360
+ if cursor.rowcount != 1:
361
+ raise RuntimeError(f"file preview rewrite expected one row | file_id={file_id} variant={variant}")
@@ -8,6 +8,7 @@ import sqlite3
8
8
  import re
9
9
 
10
10
  from app.db.version import CURRENT_DATABASE_VERSION
11
+ from app.db.editable_files_migration import add_editable_file_schema
11
12
  from app.db.retire_sounds import retire_sounds
12
13
  from app.db.schema import create_namespace_content_migrations_table
13
14
  from app.services.encryption import EncryptionService
@@ -382,6 +383,7 @@ _MIGRATIONS: dict[
382
383
  6: _migration_6_to_7,
383
384
  7: _migration_7_to_8,
384
385
  8: retire_sounds,
386
+ 9: add_editable_file_schema,
385
387
  }
386
388
 
387
389
 
@@ -1,3 +1,3 @@
1
1
  """Database schema version constants without runtime configuration imports."""
2
2
 
3
- CURRENT_DATABASE_VERSION = 9
3
+ CURRENT_DATABASE_VERSION = 10