MetaList 0.3.4__tar.gz → 0.3.7__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 (328) hide show
  1. {metalist-0.3.4 → metalist-0.3.7}/MANIFEST.in +1 -0
  2. {metalist-0.3.4/metalist.egg-info → metalist-0.3.7}/PKG-INFO +5 -5
  3. {metalist-0.3.4 → metalist-0.3.7}/README.md +4 -4
  4. {metalist-0.3.4 → metalist-0.3.7}/app/api/middleware/auth.py +2 -0
  5. {metalist-0.3.4 → metalist-0.3.7}/app/api/routes/auth.py +99 -2
  6. {metalist-0.3.4 → metalist-0.3.7}/app/api/routes/backups.py +135 -26
  7. {metalist-0.3.4 → metalist-0.3.7}/app/main.py +88 -32
  8. {metalist-0.3.4 → metalist-0.3.7}/app/server_runtime.py +5 -0
  9. {metalist-0.3.4 → metalist-0.3.7}/app/services/namespace_deletion_worker.py +32 -3
  10. metalist-0.3.7/app/services/namespace_rename_jobs.py +115 -0
  11. metalist-0.3.7/app/services/namespace_rename_worker.py +160 -0
  12. {metalist-0.3.4 → metalist-0.3.7}/app/services/namespace_switcher.py +247 -23
  13. {metalist-0.3.4 → metalist-0.3.7}/app/static/config/command_palette_tags.json +4 -0
  14. {metalist-0.3.4 → metalist-0.3.7}/app/static/css/main.css +0 -13
  15. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/auth.js +23 -3
  16. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/command-palette/command-palette-controller.js +15 -0
  17. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/command-palette/endpoint-registry.js +7 -0
  18. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/config.js +2 -0
  19. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/backup-restore-modal.js +151 -83
  20. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/delete-namespace-modal.js +53 -4
  21. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/delete-namespace-validation.js +10 -3
  22. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/namespace-modals.js +167 -1
  23. metalist-0.3.7/app/static/js/modules/modals/namespace-ports-profile.js +17 -0
  24. metalist-0.3.7/app/static/js/modules/modals/namespace-rename-validation.js +32 -0
  25. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/namespace-deleted.js +29 -3
  26. metalist-0.3.7/app/static/js/namespace-renamed.js +116 -0
  27. {metalist-0.3.4 → metalist-0.3.7}/app/templates/namespace_deleted.html +1 -1
  28. metalist-0.3.7/app/templates/namespace_renamed.html +67 -0
  29. {metalist-0.3.4 → metalist-0.3.7}/app/version.py +1 -1
  30. {metalist-0.3.4 → metalist-0.3.7}/convert-from-legacy.py +91 -11
  31. {metalist-0.3.4 → metalist-0.3.7}/main.py +23 -21
  32. {metalist-0.3.4 → metalist-0.3.7/metalist.egg-info}/PKG-INFO +5 -5
  33. {metalist-0.3.4 → metalist-0.3.7}/metalist.egg-info/SOURCES.txt +6 -0
  34. {metalist-0.3.4 → metalist-0.3.7}/pyproject.toml +2 -0
  35. {metalist-0.3.4 → metalist-0.3.7}/app/__init__.py +0 -0
  36. {metalist-0.3.4 → metalist-0.3.7}/app/api/__init__.py +0 -0
  37. {metalist-0.3.4 → metalist-0.3.7}/app/api/deps.py +0 -0
  38. {metalist-0.3.4 → metalist-0.3.7}/app/api/dev.py +0 -0
  39. {metalist-0.3.4 → metalist-0.3.7}/app/api/request_auth.py +0 -0
  40. {metalist-0.3.4 → metalist-0.3.7}/app/api/routes/files.py +0 -0
  41. {metalist-0.3.4 → metalist-0.3.7}/app/api/routes/mcp.py +0 -0
  42. {metalist-0.3.4 → metalist-0.3.7}/app/api/routes/memory.py +0 -0
  43. {metalist-0.3.4 → metalist-0.3.7}/app/api/routes/notes.py +0 -0
  44. {metalist-0.3.4 → metalist-0.3.7}/app/api/routes/ontology.py +0 -0
  45. {metalist-0.3.4 → metalist-0.3.7}/app/api/routes/reminders.py +0 -0
  46. {metalist-0.3.4 → metalist-0.3.7}/app/api/routes/sounds.py +0 -0
  47. {metalist-0.3.4 → metalist-0.3.7}/app/api/routes/test.py +0 -0
  48. {metalist-0.3.4 → metalist-0.3.7}/app/api/transactions.py +0 -0
  49. {metalist-0.3.4 → metalist-0.3.7}/app/config.py +0 -0
  50. {metalist-0.3.4 → metalist-0.3.7}/app/db/__init__.py +0 -0
  51. {metalist-0.3.4 → metalist-0.3.7}/app/db/backup_settings_sql.py +0 -0
  52. {metalist-0.3.4 → metalist-0.3.7}/app/db/engine.py +0 -0
  53. {metalist-0.3.4 → metalist-0.3.7}/app/db/file_schema.py +0 -0
  54. {metalist-0.3.4 → metalist-0.3.7}/app/db/file_session.py +0 -0
  55. {metalist-0.3.4 → metalist-0.3.7}/app/db/files_sql.py +0 -0
  56. {metalist-0.3.4 → metalist-0.3.7}/app/db/link_titles_sql.py +0 -0
  57. {metalist-0.3.4 → metalist-0.3.7}/app/db/migrations.py +0 -0
  58. {metalist-0.3.4 → metalist-0.3.7}/app/db/notes_sql.py +0 -0
  59. {metalist-0.3.4 → metalist-0.3.7}/app/db/ontology_rules_sql.py +0 -0
  60. {metalist-0.3.4 → metalist-0.3.7}/app/db/reminders_sql.py +0 -0
  61. {metalist-0.3.4 → metalist-0.3.7}/app/db/schema.py +0 -0
  62. {metalist-0.3.4 → metalist-0.3.7}/app/db/search_history_sql.py +0 -0
  63. {metalist-0.3.4 → metalist-0.3.7}/app/db/session.py +0 -0
  64. {metalist-0.3.4 → metalist-0.3.7}/app/db/settings_sql.py +0 -0
  65. {metalist-0.3.4 → metalist-0.3.7}/app/db/sounds_sql.py +0 -0
  66. {metalist-0.3.4 → metalist-0.3.7}/app/db/tab_state_sql.py +0 -0
  67. {metalist-0.3.4 → metalist-0.3.7}/app/encryption_audit.py +0 -0
  68. {metalist-0.3.4 → metalist-0.3.7}/app/mcp/__init__.py +0 -0
  69. {metalist-0.3.4 → metalist-0.3.7}/app/mcp/__main__.py +0 -0
  70. {metalist-0.3.4 → metalist-0.3.7}/app/mcp/errors.py +0 -0
  71. {metalist-0.3.4 → metalist-0.3.7}/app/mcp/policy.py +0 -0
  72. {metalist-0.3.4 → metalist-0.3.7}/app/mcp/read_service.py +0 -0
  73. {metalist-0.3.4 → metalist-0.3.7}/app/mcp/server.py +0 -0
  74. {metalist-0.3.4 → metalist-0.3.7}/app/mcp/tools.py +0 -0
  75. {metalist-0.3.4 → metalist-0.3.7}/app/models/commands.py +0 -0
  76. {metalist-0.3.4 → metalist-0.3.7}/app/models/database.py +0 -0
  77. {metalist-0.3.4 → metalist-0.3.7}/app/models/entities.py +0 -0
  78. {metalist-0.3.4 → metalist-0.3.7}/app/models/enums.py +0 -0
  79. {metalist-0.3.4 → metalist-0.3.7}/app/models/linked_list.py +0 -0
  80. {metalist-0.3.4 → metalist-0.3.7}/app/models/list_operations.py +0 -0
  81. {metalist-0.3.4 → metalist-0.3.7}/app/models/list_traversal.py +0 -0
  82. {metalist-0.3.4 → metalist-0.3.7}/app/models/note_crud.py +0 -0
  83. {metalist-0.3.4 → metalist-0.3.7}/app/models/utils.py +0 -0
  84. {metalist-0.3.4 → metalist-0.3.7}/app/presentation/__init__.py +0 -0
  85. {metalist-0.3.4 → metalist-0.3.7}/app/presentation/render/__init__.py +0 -0
  86. {metalist-0.3.4 → metalist-0.3.7}/app/presentation/render/note_renderer.py +0 -0
  87. {metalist-0.3.4 → metalist-0.3.7}/app/presentation/templates.py +0 -0
  88. {metalist-0.3.4 → metalist-0.3.7}/app/security/encryption.py +0 -0
  89. {metalist-0.3.4 → metalist-0.3.7}/app/services/__init__.py +0 -0
  90. {metalist-0.3.4 → metalist-0.3.7}/app/services/auth.py +0 -0
  91. {metalist-0.3.4 → metalist-0.3.7}/app/services/auth_cache_state.py +0 -0
  92. {metalist-0.3.4 → metalist-0.3.7}/app/services/auth_service.py +0 -0
  93. {metalist-0.3.4 → metalist-0.3.7}/app/services/backlinks.py +0 -0
  94. {metalist-0.3.4 → metalist-0.3.7}/app/services/backup_service.py +0 -0
  95. {metalist-0.3.4 → metalist-0.3.7}/app/services/backup_settings_service.py +0 -0
  96. {metalist-0.3.4 → metalist-0.3.7}/app/services/base_service.py +0 -0
  97. {metalist-0.3.4 → metalist-0.3.7}/app/services/client_state_service.py +0 -0
  98. {metalist-0.3.4 → metalist-0.3.7}/app/services/content_cache.py +0 -0
  99. {metalist-0.3.4 → metalist-0.3.7}/app/services/content_formatting.py +0 -0
  100. {metalist-0.3.4 → metalist-0.3.7}/app/services/date_filtering.py +0 -0
  101. {metalist-0.3.4 → metalist-0.3.7}/app/services/dependencies.py +0 -0
  102. {metalist-0.3.4 → metalist-0.3.7}/app/services/diagnostics.py +0 -0
  103. {metalist-0.3.4 → metalist-0.3.7}/app/services/embedded_references.py +0 -0
  104. {metalist-0.3.4 → metalist-0.3.7}/app/services/encryption.py +0 -0
  105. {metalist-0.3.4 → metalist-0.3.7}/app/services/exception_capture.py +0 -0
  106. {metalist-0.3.4 → metalist-0.3.7}/app/services/file_registry.py +0 -0
  107. {metalist-0.3.4 → metalist-0.3.7}/app/services/file_storage.py +0 -0
  108. {metalist-0.3.4 → metalist-0.3.7}/app/services/html_export.py +0 -0
  109. {metalist-0.3.4 → metalist-0.3.7}/app/services/html_unformatting.py +0 -0
  110. {metalist-0.3.4 → metalist-0.3.7}/app/services/hydration_state.py +0 -0
  111. {metalist-0.3.4 → metalist-0.3.7}/app/services/integrity.py +0 -0
  112. {metalist-0.3.4 → metalist-0.3.7}/app/services/latex_rendering.py +0 -0
  113. {metalist-0.3.4 → metalist-0.3.7}/app/services/link_titles.py +0 -0
  114. {metalist-0.3.4 → metalist-0.3.7}/app/services/login_rate_limit.py +0 -0
  115. {metalist-0.3.4 → metalist-0.3.7}/app/services/maintenance_mode.py +0 -0
  116. {metalist-0.3.4 → metalist-0.3.7}/app/services/markdown_rendering.py +0 -0
  117. {metalist-0.3.4 → metalist-0.3.7}/app/services/memory.py +0 -0
  118. {metalist-0.3.4 → metalist-0.3.7}/app/services/memory_service.py +0 -0
  119. {metalist-0.3.4 → metalist-0.3.7}/app/services/namespace_deletion_jobs.py +0 -0
  120. {metalist-0.3.4 → metalist-0.3.7}/app/services/note_image_tags.py +0 -0
  121. {metalist-0.3.4 → metalist-0.3.7}/app/services/note_service.py +0 -0
  122. {metalist-0.3.4 → metalist-0.3.7}/app/services/note_store.py +0 -0
  123. {metalist-0.3.4 → metalist-0.3.7}/app/services/ontology_rules_store.py +0 -0
  124. {metalist-0.3.4 → metalist-0.3.7}/app/services/query_service.py +0 -0
  125. {metalist-0.3.4 → metalist-0.3.7}/app/services/reminders.py +0 -0
  126. {metalist-0.3.4 → metalist-0.3.7}/app/services/root_sorting.py +0 -0
  127. {metalist-0.3.4 → metalist-0.3.7}/app/services/runtime_hardening.py +0 -0
  128. {metalist-0.3.4 → metalist-0.3.7}/app/services/search_history.py +0 -0
  129. {metalist-0.3.4 → metalist-0.3.7}/app/services/search_index.py +0 -0
  130. {metalist-0.3.4 → metalist-0.3.7}/app/services/search_query.py +0 -0
  131. {metalist-0.3.4 → metalist-0.3.7}/app/services/search_text.py +0 -0
  132. {metalist-0.3.4 → metalist-0.3.7}/app/services/session_timeout_service.py +0 -0
  133. {metalist-0.3.4 → metalist-0.3.7}/app/services/shell_session_service.py +0 -0
  134. {metalist-0.3.4 → metalist-0.3.7}/app/services/snapshot.py +0 -0
  135. {metalist-0.3.4 → metalist-0.3.7}/app/services/sound_storage.py +0 -0
  136. {metalist-0.3.4 → metalist-0.3.7}/app/services/store.py +0 -0
  137. {metalist-0.3.4 → metalist-0.3.7}/app/services/sync.py +0 -0
  138. {metalist-0.3.4 → metalist-0.3.7}/app/services/sync_state.py +0 -0
  139. {metalist-0.3.4 → metalist-0.3.7}/app/services/tab_state.py +0 -0
  140. {metalist-0.3.4 → metalist-0.3.7}/app/services/tag_case.py +0 -0
  141. {metalist-0.3.4 → metalist-0.3.7}/app/services/tag_ontology.py +0 -0
  142. {metalist-0.3.4 → metalist-0.3.7}/app/services/tag_rename.py +0 -0
  143. {metalist-0.3.4 → metalist-0.3.7}/app/services/tag_suggestions.py +0 -0
  144. {metalist-0.3.4 → metalist-0.3.7}/app/services/tag_term_matching.py +0 -0
  145. {metalist-0.3.4 → metalist-0.3.7}/app/services/test_reset.py +0 -0
  146. {metalist-0.3.4 → metalist-0.3.7}/app/services/tokens.py +0 -0
  147. {metalist-0.3.4 → metalist-0.3.7}/app/services/transaction_manager.py +0 -0
  148. {metalist-0.3.4 → metalist-0.3.7}/app/services/undo_service.py +0 -0
  149. {metalist-0.3.4 → metalist-0.3.7}/app/services/undo_state.py +0 -0
  150. {metalist-0.3.4 → metalist-0.3.7}/app/services/view_cache.py +0 -0
  151. {metalist-0.3.4 → metalist-0.3.7}/app/services/view_diff.py +0 -0
  152. {metalist-0.3.4 → metalist-0.3.7}/app/services/view_state.py +0 -0
  153. {metalist-0.3.4 → metalist-0.3.7}/app/services/windows_process_control.py +0 -0
  154. {metalist-0.3.4 → metalist-0.3.7}/app/startup_js_sanity.py +0 -0
  155. {metalist-0.3.4 → metalist-0.3.7}/app/startup_sanity.py +0 -0
  156. {metalist-0.3.4 → metalist-0.3.7}/app/startup_sanity_config.py +0 -0
  157. {metalist-0.3.4 → metalist-0.3.7}/app/static/css/forms.css +0 -0
  158. {metalist-0.3.4 → metalist-0.3.7}/app/static/img/favicon.ico +0 -0
  159. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/locked.js +0 -0
  160. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/main.js +0 -0
  161. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/activity-tracker.js +0 -0
  162. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/api-client.js +0 -0
  163. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/async-result.js +0 -0
  164. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/client-state-api.js +0 -0
  165. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/client-state-migration.js +0 -0
  166. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
  167. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/command-palette/preferences-store.js +0 -0
  168. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/command-palette/tag-config-loader.js +0 -0
  169. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/command-palette/usage-store.js +0 -0
  170. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/connectivity-monitor.js +0 -0
  171. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/context-menu/context-menu-registry.js +0 -0
  172. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/context-menu/context-menu-service.js +0 -0
  173. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
  174. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/dom-utils-fixed.js +0 -0
  175. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/dom-utils.js +0 -0
  176. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/editor-commands.js +0 -0
  177. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/editor-selection.js +0 -0
  178. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/editor-toolbar.js +0 -0
  179. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/error-handler.js +0 -0
  180. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/error-overlay.js +0 -0
  181. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/location-flags.js +0 -0
  182. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/login-namespace-picker.js +0 -0
  183. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
  184. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/backup-result-modal.js +0 -0
  185. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
  186. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/backup-settings-modal.js +0 -0
  187. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/base-modal.js +0 -0
  188. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/help-modal.js +0 -0
  189. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/memory-modal.js +0 -0
  190. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/namespace-loading-page.js +0 -0
  191. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/ontology-modal.js +0 -0
  192. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/password-modal.js +0 -0
  193. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/prioritize-modal.js +0 -0
  194. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/random-password-modal.js +0 -0
  195. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/reminder-modal.js +0 -0
  196. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
  197. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
  198. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/sound-manager-modal.js +0 -0
  199. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/modals/version-info-modal.js +0 -0
  200. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/actions/content-actions.js +0 -0
  201. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
  202. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/actions/note-actions.js +0 -0
  203. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
  204. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
  205. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/actions/ui-actions.js +0 -0
  206. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/events/context-menu-events.js +0 -0
  207. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/events/focus-events.js +0 -0
  208. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
  209. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
  210. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/events/keyboard-events.js +0 -0
  211. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/events/mouse-events.js +0 -0
  212. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
  213. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/mode-context.js +0 -0
  214. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/mode-logger.js +0 -0
  215. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
  216. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
  217. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
  218. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
  219. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
  220. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
  221. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -0
  222. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +0 -0
  223. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/date-filter-indicator-service.js +0 -0
  224. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/date-filter-service.js +0 -0
  225. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/differential-view-service.js +0 -0
  226. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
  227. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
  228. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
  229. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
  230. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
  231. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
  232. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +0 -0
  233. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +0 -0
  234. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +0 -0
  235. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
  236. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
  237. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
  238. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
  239. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
  240. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
  241. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
  242. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
  243. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
  244. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
  245. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
  246. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
  247. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/rhs-panel-service.js +0 -0
  248. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
  249. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
  250. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
  251. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
  252. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
  253. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
  254. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
  255. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
  256. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
  257. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
  258. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
  259. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +0 -0
  260. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
  261. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
  262. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
  263. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
  264. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -0
  265. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/tag-bar-service.js +0 -0
  266. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +0 -0
  267. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +0 -0
  268. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
  269. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
  270. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/password-generator.js +0 -0
  271. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/reminder-store.js +0 -0
  272. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/reminder-surface-service.js +0 -0
  273. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/selection-markers.js +0 -0
  274. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/session-auth.js +0 -0
  275. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/sound-service.js +0 -0
  276. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/tag-token.js +0 -0
  277. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/modules/uuid.js +0 -0
  278. {metalist-0.3.4 → metalist-0.3.7}/app/static/js/vendor/markdown-it.min.js +0 -0
  279. {metalist-0.3.4 → metalist-0.3.7}/app/static/video/login-startup.mp4 +0 -0
  280. {metalist-0.3.4 → metalist-0.3.7}/app/templates/base.html +0 -0
  281. {metalist-0.3.4 → metalist-0.3.7}/app/templates/index.html +0 -0
  282. {metalist-0.3.4 → metalist-0.3.7}/app/templates/locked.html +0 -0
  283. {metalist-0.3.4 → metalist-0.3.7}/app/templates/maintenance.html +0 -0
  284. {metalist-0.3.4 → metalist-0.3.7}/app/templates/notes_list.html +0 -0
  285. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/__init__.py +0 -0
  286. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/alphabetize_root_notes.py +0 -0
  287. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/base.py +0 -0
  288. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/collapse.py +0 -0
  289. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/copy_note.py +0 -0
  290. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/create_child.py +0 -0
  291. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/create_note.py +0 -0
  292. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/create_sibling.py +0 -0
  293. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/delete_subtree.py +0 -0
  294. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/expand.py +0 -0
  295. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/indent.py +0 -0
  296. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/lock.py +0 -0
  297. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/move.py +0 -0
  298. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/move_to_top.py +0 -0
  299. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/outdent.py +0 -0
  300. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/paste_child.py +0 -0
  301. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/paste_sibling.py +0 -0
  302. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/prioritize.py +0 -0
  303. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/record_edit_mode.py +0 -0
  304. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/redo.py +0 -0
  305. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/rename_tag.py +0 -0
  306. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/reset_updated_at.py +0 -0
  307. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/run_shell.py +0 -0
  308. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/search_comment_autofill.py +0 -0
  309. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/search_context_tags.py +0 -0
  310. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/set_collapse_bulk.py +0 -0
  311. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/set_collapse_in_context.py +0 -0
  312. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/split_note.py +0 -0
  313. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/toggle_reference_mode.py +0 -0
  314. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/toggle_todo_done.py +0 -0
  315. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/undo.py +0 -0
  316. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/unformat_content.py +0 -0
  317. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/update_content.py +0 -0
  318. {metalist-0.3.4 → metalist-0.3.7}/app/usecases/view.py +0 -0
  319. {metalist-0.3.4 → metalist-0.3.7}/app/utils/__init__.py +0 -0
  320. {metalist-0.3.4 → metalist-0.3.7}/app/utils/encryption.py +0 -0
  321. {metalist-0.3.4 → metalist-0.3.7}/app/utils/text_utils.py +0 -0
  322. {metalist-0.3.4 → metalist-0.3.7}/mcp_client.py +0 -0
  323. {metalist-0.3.4 → metalist-0.3.7}/metalist.egg-info/dependency_links.txt +0 -0
  324. {metalist-0.3.4 → metalist-0.3.7}/metalist.egg-info/entry_points.txt +0 -0
  325. {metalist-0.3.4 → metalist-0.3.7}/metalist.egg-info/requires.txt +0 -0
  326. {metalist-0.3.4 → metalist-0.3.7}/metalist.egg-info/top_level.txt +0 -0
  327. {metalist-0.3.4 → metalist-0.3.7}/scripts/generate-lan-cert.sh +0 -0
  328. {metalist-0.3.4 → metalist-0.3.7}/setup.cfg +0 -0
@@ -1,3 +1,4 @@
1
1
  recursive-include app/static *
2
2
  recursive-include app/templates *.html
3
+ prune app/__pycache__
3
4
  global-exclude .DS_Store
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metalist
3
- Version: 0.3.4
3
+ Version: 0.3.7
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
@@ -234,9 +234,9 @@ Tool catalog and schemas:
234
234
  - `docs/mcp_tools.md`
235
235
 
236
236
  ### Legacy Import
237
- `convert-from-legacy.py` replaces the SQLite database referenced by `app.config.DATABASE_URL` and imports notes from a legacy JSON export.
237
+ `convert-from-legacy.py` replaces the SQLite database referenced by `app.config.DATABASE_URL`, clears its files/sounds sidecar, and imports notes from a legacy JSON export.
238
238
 
239
- This is destructive. It deletes the existing DB file before rebuilding it.
239
+ This is destructive. It deletes the existing notes DB and related files/sounds DB before rebuilding the namespace.
240
240
 
241
241
  Example usage:
242
242
  ```bash
@@ -263,11 +263,11 @@ This repo now packages itself under the PyPI distribution name `metalist`. Curre
263
263
 
264
264
  Recommended release path:
265
265
  1. In the existing PyPI project `metalist`, configure GitHub Trusted Publishing for `evolvingstuff/metalist` and the workflow file `.github/workflows/publish-pypi.yml`.
266
- 2. Push a tag such as `v0.3.4`.
266
+ 2. Push a tag such as `v0.3.5`.
267
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`.
268
268
 
269
269
  If `--input` is omitted, a file picker opens (when `tkinter` is available).
270
- Notes tagged with `@implies` are converted into ontology rules and are not imported as notes.
270
+ Notes tagged with `@implies` are converted into ontology rules and are not imported as notes. Legacy rules that are invalid under the current ontology grammar are reported and skipped while valid rules continue importing.
271
271
 
272
272
  ### Run Tests
273
273
 
@@ -205,9 +205,9 @@ Tool catalog and schemas:
205
205
  - `docs/mcp_tools.md`
206
206
 
207
207
  ### Legacy Import
208
- `convert-from-legacy.py` replaces the SQLite database referenced by `app.config.DATABASE_URL` and imports notes from a legacy JSON export.
208
+ `convert-from-legacy.py` replaces the SQLite database referenced by `app.config.DATABASE_URL`, clears its files/sounds sidecar, and imports notes from a legacy JSON export.
209
209
 
210
- This is destructive. It deletes the existing DB file before rebuilding it.
210
+ This is destructive. It deletes the existing notes DB and related files/sounds DB before rebuilding the namespace.
211
211
 
212
212
  Example usage:
213
213
  ```bash
@@ -234,11 +234,11 @@ This repo now packages itself under the PyPI distribution name `metalist`. Curre
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.4`.
237
+ 2. Push a tag such as `v0.3.5`.
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).
241
- Notes tagged with `@implies` are converted into ontology rules and are not imported as notes.
241
+ Notes tagged with `@implies` are converted into ontology rules and are not imported as notes. Legacy rules that are invalid under the current ontology grammar are reported and skipped while valid rules continue importing.
242
242
 
243
243
  ### Run Tests
244
244
 
@@ -20,10 +20,12 @@ class AuthMiddleware(BaseHTTPMiddleware):
20
20
  f"{API_PREFIX}/auth/status",
21
21
  f"{API_PREFIX}/auth/session",
22
22
  f"{API_PREFIX}/auth/namespaces/delete-jobs/",
23
+ f"{API_PREFIX}/auth/namespaces/rename-jobs/",
23
24
  f"{API_PREFIX}/mcp",
24
25
  "/mcp-client-v2",
25
26
  "/mcp-client",
26
27
  "/namespace-deleted",
28
+ "/namespace-renamed",
27
29
  "/static/", # CSS/JS files needed for login page
28
30
  "/favicon.ico",
29
31
  "/locked",
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  import secrets
4
5
  import sqlite3
5
6
  from fastapi import APIRouter, Depends, Header, HTTPException, Request, Response
@@ -46,8 +47,10 @@ from app.services.namespace_switcher import delete_current_namespace
46
47
  from app.services.namespace_switcher import delete_namespace
47
48
  from app.services.namespace_switcher import open_login_namespace
48
49
  from app.services.namespace_switcher import open_or_launch_namespace
50
+ from app.services.namespace_switcher import rename_current_namespace
49
51
  from app.services.namespace_switcher import save_namespace_port_profiles
50
52
  from app.services.namespace_deletion_jobs import load_namespace_deletion_job
53
+ from app.services.namespace_rename_jobs import load_namespace_rename_job
51
54
  from app.server_runtime import NamespaceLaunchProfile
52
55
  from app.server_runtime import resolve_namespace_directory
53
56
  from app.server_runtime import resolve_namespaced_database_path
@@ -88,6 +91,7 @@ from app.services.client_state_service import save_command_palette_usage
88
91
 
89
92
 
90
93
  router = APIRouter(prefix="/auth", tags=["auth2"])
94
+ logger = logging.getLogger(__name__)
91
95
 
92
96
 
93
97
  class LoginRequest(BaseModel):
@@ -437,7 +441,13 @@ def _on_hydration_done(future: Future) -> None:
437
441
  return
438
442
  error = future.exception()
439
443
  if error is not None:
440
- hydration_state.fail(str(error))
444
+ error_message = f"{type(error).__name__}: {error}"
445
+ logger.error(
446
+ "Hydration worker failed: %s",
447
+ error_message,
448
+ exc_info=(type(error), error, error.__traceback__),
449
+ )
450
+ hydration_state.fail(error_message)
441
451
 
442
452
 
443
453
  def _start_hydration(*, first_load: bool, rebuild_required: bool) -> None:
@@ -582,6 +592,15 @@ def _require_string_field(payload: dict[str, object], field_name: str) -> str:
582
592
  return value
583
593
 
584
594
 
595
+ def _require_string_field_allow_empty(payload: dict[str, object], field_name: str) -> str:
596
+ if field_name not in payload:
597
+ raise HTTPException(status_code=400, detail=f"{field_name} is required")
598
+ value = payload[field_name]
599
+ if not isinstance(value, str):
600
+ raise HTTPException(status_code=400, detail=f"{field_name} must be a string")
601
+ return value
602
+
603
+
585
604
  def _require_int_field(payload: dict[str, object], field_name: str) -> int:
586
605
  if field_name not in payload:
587
606
  raise HTTPException(status_code=400, detail=f"{field_name} is required")
@@ -960,7 +979,8 @@ def save_namespace_ports(
960
979
  def _delete_namespace_from_body(*, body: dict[str, object], target_namespace: str) -> dict[str, object]:
961
980
  normalized_target_namespace = validate_namespace(namespace=target_namespace)
962
981
  confirmed_namespace = _require_string_field(body, "confirmed_namespace")
963
- current_password = _require_string_field(body, "current_password")
982
+ current_password = _require_string_field_allow_empty(body, "current_password")
983
+ redirect_namespace = _require_string_field(body, "redirect_namespace")
964
984
 
965
985
  if _namespace_requires_password(namespace=normalized_target_namespace):
966
986
  _verify_namespace_password(
@@ -981,6 +1001,7 @@ def _delete_namespace_from_body(*, body: dict[str, object], target_namespace: st
981
1001
  current_namespace=ACTIVE_NAMESPACE,
982
1002
  target_namespace=normalized_target_namespace,
983
1003
  confirmed_namespace=confirmed_namespace,
1004
+ redirect_namespace=redirect_namespace,
984
1005
  )
985
1006
  if delete_capture.captured_exception is not None:
986
1007
  exc = delete_capture.captured_exception
@@ -1011,11 +1032,33 @@ def namespace_delete_preflight(
1011
1032
  target_requires_password = False
1012
1033
  if target_exists:
1013
1034
  target_requires_password = _namespace_requires_password(namespace=target_namespace)
1035
+ catalog = build_namespace_catalog(
1036
+ environ=os.environ,
1037
+ current_namespace=ACTIVE_NAMESPACE,
1038
+ )
1039
+ raw_namespaces = catalog["namespaces"]
1040
+ if not isinstance(raw_namespaces, list):
1041
+ raise RuntimeError("Namespace catalog missing namespaces")
1042
+ redirect_namespaces: list[str] = []
1043
+ for raw_entry in raw_namespaces:
1044
+ if not isinstance(raw_entry, dict):
1045
+ raise RuntimeError("Namespace catalog entry must be an object")
1046
+ namespace = raw_entry["namespace"]
1047
+ if not isinstance(namespace, str) or namespace == "":
1048
+ raise RuntimeError("Namespace catalog entry missing namespace")
1049
+ if namespace != target_namespace:
1050
+ redirect_namespaces.append(namespace)
1051
+ redirect_namespaces.sort()
1052
+ recreates_default = target_namespace == ACTIVE_NAMESPACE and len(redirect_namespaces) == 0
1053
+ if recreates_default:
1054
+ redirect_namespaces.append("default")
1014
1055
  return {
1015
1056
  "target_namespace": target_namespace,
1016
1057
  "target_exists": target_exists,
1017
1058
  "target_requires_password": target_requires_password,
1018
1059
  "is_current_namespace": target_namespace == ACTIVE_NAMESPACE,
1060
+ "redirect_namespaces": redirect_namespaces,
1061
+ "recreates_default": recreates_default,
1019
1062
  }
1020
1063
 
1021
1064
 
@@ -1046,6 +1089,41 @@ def delete_active_namespace(
1046
1089
  )
1047
1090
 
1048
1091
 
1092
+ @router.post("/namespaces/rename-current")
1093
+ @transactional_route
1094
+ def rename_active_namespace(
1095
+ payload: dict[str, object],
1096
+ token: Annotated[str, Depends(_require_auth)],
1097
+ ):
1098
+ body = _require_body_object(payload)
1099
+ target_namespace = _require_string_field(body, "target_namespace")
1100
+ rename_capture = CapturedExceptionContext(
1101
+ RuntimeError,
1102
+ ValueError,
1103
+ TypeError,
1104
+ FileNotFoundError,
1105
+ )
1106
+ result = None
1107
+ with rename_capture:
1108
+ result = rename_current_namespace(
1109
+ environ=os.environ,
1110
+ current_namespace=ACTIVE_NAMESPACE,
1111
+ target_namespace=target_namespace,
1112
+ )
1113
+ if rename_capture.captured_exception is not None:
1114
+ exc = rename_capture.captured_exception
1115
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
1116
+ if result is None:
1117
+ raise RuntimeError("Namespace rename did not return a result")
1118
+ return {
1119
+ "source_namespace": result.source_namespace,
1120
+ "target_namespace": result.target_namespace,
1121
+ "redirect_url": result.redirect_url,
1122
+ "rename_job_id": result.rename_job_id,
1123
+ "message": result.message,
1124
+ }
1125
+
1126
+
1049
1127
  @router.get("/namespaces/delete-jobs/{job_id}")
1050
1128
  def namespace_delete_job_status(
1051
1129
  job_id: str,
@@ -1067,6 +1145,25 @@ def namespace_delete_job_status(
1067
1145
  return job_record
1068
1146
 
1069
1147
 
1148
+ @router.get("/namespaces/rename-jobs/{job_id}")
1149
+ def namespace_rename_job_status(job_id: str):
1150
+ job_record_capture = CapturedExceptionContext(
1151
+ RuntimeError,
1152
+ TypeError,
1153
+ ValueError,
1154
+ FileNotFoundError,
1155
+ )
1156
+ job_record: dict[str, object] | None = None
1157
+ with job_record_capture:
1158
+ job_record = load_namespace_rename_job(job_id=job_id)
1159
+ if job_record_capture.captured_exception is not None:
1160
+ exc = job_record_capture.captured_exception
1161
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
1162
+ if job_record is None:
1163
+ raise HTTPException(status_code=404, detail=f"Namespace rename job not found: {job_id}")
1164
+ return job_record
1165
+
1166
+
1070
1167
  @router.post("/hydrate", response_model=HydrationStatusResponse)
1071
1168
  @transactional_route
1072
1169
  def hydrate_cache(
@@ -35,6 +35,7 @@ from app.server_runtime import NamespaceLaunchProfile
35
35
  from app.server_runtime import _DEFAULT_HTTP_PORT
36
36
  from app.server_runtime import _DEFAULT_MCP_AGENT_WEB_PORT
37
37
  from app.server_runtime import load_all_namespace_launch_profiles
38
+ from app.server_runtime import load_namespace_launch_profile
38
39
  from app.server_runtime import resolve_namespace_directory
39
40
  from app.server_runtime import resolve_namespaced_database_path
40
41
  from app.server_runtime import resolve_namespaces_directory
@@ -125,6 +126,7 @@ class BackupRestorePreflightResponse(BaseModel):
125
126
  backup_namespace: str
126
127
  target_namespace: str
127
128
  same_namespace: bool
129
+ target_is_active: bool
128
130
  target_exists: bool
129
131
  target_requires_password: bool
130
132
  restored_profile: BackupRestoreLaunchProfileResponse | None
@@ -699,6 +701,48 @@ def _response_profile_from_backup_profile(
699
701
  )
700
702
 
701
703
 
704
+ def _required_existing_target_launch_profile(*, target_namespace: str) -> BackupLaunchProfile:
705
+ profile = load_namespace_launch_profile(namespace=target_namespace)
706
+ if profile is None:
707
+ raise RuntimeError(f"Existing target namespace has no saved launch profile: {target_namespace}")
708
+ if profile.port is None:
709
+ raise RuntimeError(f"Existing target namespace has no saved HTTP port: {target_namespace}")
710
+ if profile.mcp_port is None:
711
+ raise RuntimeError(f"Existing target namespace has no saved MCP port: {target_namespace}")
712
+ return BackupLaunchProfile(
713
+ namespace=target_namespace,
714
+ port=profile.port,
715
+ https_port=profile.https_port,
716
+ mcp_port=profile.mcp_port,
717
+ )
718
+
719
+
720
+ def _resolve_same_name_restore_launch_profile(
721
+ *,
722
+ backup_path: Path,
723
+ target_namespace: str,
724
+ target_exists: bool,
725
+ ) -> BackupLaunchProfile:
726
+ if target_exists:
727
+ return _required_existing_target_launch_profile(
728
+ target_namespace=target_namespace,
729
+ )
730
+ restored_profile = read_backup_launch_profile(
731
+ backup_path,
732
+ expected_namespace=target_namespace,
733
+ )
734
+ suggested_profile = _suggest_import_launch_profile(
735
+ target_namespace=target_namespace,
736
+ restored_profile=restored_profile,
737
+ )
738
+ return BackupLaunchProfile(
739
+ namespace=target_namespace,
740
+ port=suggested_profile.port,
741
+ https_port=suggested_profile.https_port,
742
+ mcp_port=suggested_profile.mcp_port,
743
+ )
744
+
745
+
702
746
  def _backup_profile_from_request(
703
747
  *,
704
748
  namespace: str,
@@ -725,6 +769,27 @@ def _assert_import_profile_has_no_internal_overlap(*, profile: BackupLaunchProfi
725
769
  )
726
770
 
727
771
 
772
+ def _restore_import_to_target(
773
+ *,
774
+ backup_path: Path,
775
+ target_database_path: Path,
776
+ backup_namespace: str,
777
+ target_namespace: str,
778
+ launch_profile: BackupLaunchProfile,
779
+ ) -> None:
780
+ restore_backup_to_paths_from_namespace(
781
+ backup_path,
782
+ target_database_path,
783
+ source_namespace=backup_namespace,
784
+ )
785
+ save_namespace_launch_profile(
786
+ namespace=target_namespace,
787
+ port=launch_profile.port,
788
+ https_port=launch_profile.https_port,
789
+ mcp_port=launch_profile.mcp_port,
790
+ )
791
+
792
+
728
793
  @router.get("/settings", response_model=BackupSettingsResponse)
729
794
  def get_backup_settings(token: Annotated[str, Depends(_require_auth)]):
730
795
  settings = load_backup_settings(token=token)
@@ -878,13 +943,21 @@ def restore_backup_preflight(
878
943
  backup_path,
879
944
  expected_namespace=backup_namespace,
880
945
  )
946
+ target_exists = _restore_target_exists(target_namespace=target_namespace)
881
947
  suggested_profile = None
882
948
  port_conflicts: list[str] = []
883
- if target_namespace != backup_namespace:
884
- port_conflicts = _collect_import_profile_port_conflicts(
949
+ if target_exists:
950
+ target_profile = _required_existing_target_launch_profile(
885
951
  target_namespace=target_namespace,
886
- launch_profile=restored_profile,
887
952
  )
953
+ suggested_profile = _response_profile_from_backup_profile(profile=target_profile)
954
+ assert suggested_profile is not None
955
+ else:
956
+ if restored_profile is not None:
957
+ port_conflicts = _collect_import_profile_port_conflicts(
958
+ target_namespace=target_namespace,
959
+ launch_profile=restored_profile,
960
+ )
888
961
  suggested_profile = _suggest_import_launch_profile(
889
962
  target_namespace=target_namespace,
890
963
  restored_profile=restored_profile,
@@ -894,7 +967,8 @@ def restore_backup_preflight(
894
967
  backup_namespace=backup_namespace,
895
968
  target_namespace=target_namespace,
896
969
  same_namespace=target_namespace == backup_namespace,
897
- target_exists=_restore_target_exists(target_namespace=target_namespace),
970
+ target_is_active=target_namespace == ACTIVE_NAMESPACE,
971
+ target_exists=target_exists,
898
972
  target_requires_password=_target_namespace_requires_password(target_namespace=target_namespace),
899
973
  restored_profile=_response_profile_from_backup_profile(profile=restored_profile),
900
974
  suggested_profile=suggested_profile,
@@ -935,10 +1009,22 @@ def restore_backup(
935
1009
  backup_path = folder_directory / folder_filename
936
1010
  if not backup_path.exists():
937
1011
  raise HTTPException(status_code=404, detail=f"Backup not found: {folder_filename}")
1012
+ target_exists = _restore_target_exists(target_namespace=target_namespace)
1013
+ launch_profile = _resolve_same_name_restore_launch_profile(
1014
+ backup_path=backup_path,
1015
+ target_namespace=target_namespace,
1016
+ target_exists=target_exists,
1017
+ )
938
1018
  if target_namespace == ACTIVE_NAMESPACE:
939
1019
  maintenance_service.enter_maintenance("Restoring backup")
940
1020
  try:
941
1021
  restore_backup_to_paths(backup_path, target_database_path)
1022
+ save_namespace_launch_profile(
1023
+ namespace=target_namespace,
1024
+ port=launch_profile.port,
1025
+ https_port=launch_profile.https_port,
1026
+ mcp_port=launch_profile.mcp_port,
1027
+ )
942
1028
  _reset_runtime_state_after_restore()
943
1029
  finally:
944
1030
  maintenance_service.exit_maintenance()
@@ -947,6 +1033,12 @@ def restore_backup(
947
1033
  open_namespace_suggested = False
948
1034
  else:
949
1035
  restore_backup_to_paths(backup_path, target_database_path)
1036
+ save_namespace_launch_profile(
1037
+ namespace=target_namespace,
1038
+ port=launch_profile.port,
1039
+ https_port=launch_profile.https_port,
1040
+ mcp_port=launch_profile.mcp_port,
1041
+ )
950
1042
  return BackupRestoreResponse(
951
1043
  backup_id=payload.backup_id,
952
1044
  source=payload.source,
@@ -999,27 +1091,44 @@ def import_backup(
999
1091
  if not backup_path.exists():
1000
1092
  raise HTTPException(status_code=404, detail=f"Backup not found: {folder_filename}")
1001
1093
 
1002
- launch_profile = _backup_profile_from_request(
1003
- namespace=target_namespace,
1004
- profile=payload.launch_profile,
1005
- )
1006
- _assert_import_profile_has_no_internal_overlap(profile=launch_profile)
1007
- _assert_import_profile_ports_do_not_conflict(
1008
- target_namespace=target_namespace,
1009
- launch_profile=launch_profile,
1010
- )
1094
+ if target_exists:
1095
+ launch_profile = _required_existing_target_launch_profile(
1096
+ target_namespace=target_namespace,
1097
+ )
1098
+ else:
1099
+ launch_profile = _backup_profile_from_request(
1100
+ namespace=target_namespace,
1101
+ profile=payload.launch_profile,
1102
+ )
1103
+ _assert_import_profile_has_no_internal_overlap(profile=launch_profile)
1104
+ _assert_import_profile_ports_do_not_conflict(
1105
+ target_namespace=target_namespace,
1106
+ launch_profile=launch_profile,
1107
+ )
1011
1108
 
1012
- restore_backup_to_paths_from_namespace(
1013
- backup_path,
1014
- target_database_path,
1015
- source_namespace=backup_namespace,
1016
- )
1017
- save_namespace_launch_profile(
1018
- namespace=target_namespace,
1019
- port=launch_profile.port,
1020
- https_port=launch_profile.https_port,
1021
- mcp_port=launch_profile.mcp_port,
1022
- )
1109
+ target_is_active = target_namespace == ACTIVE_NAMESPACE
1110
+ if target_is_active:
1111
+ maintenance_service.enter_maintenance("Importing backup")
1112
+ try:
1113
+ _restore_import_to_target(
1114
+ backup_path=backup_path,
1115
+ target_database_path=target_database_path,
1116
+ backup_namespace=backup_namespace,
1117
+ target_namespace=target_namespace,
1118
+ launch_profile=launch_profile,
1119
+ )
1120
+ _reset_runtime_state_after_restore()
1121
+ finally:
1122
+ maintenance_service.exit_maintenance()
1123
+ _schedule_server_restart_after_restore(delay_seconds=0.5)
1124
+ else:
1125
+ _restore_import_to_target(
1126
+ backup_path=backup_path,
1127
+ target_database_path=target_database_path,
1128
+ backup_namespace=backup_namespace,
1129
+ target_namespace=target_namespace,
1130
+ launch_profile=launch_profile,
1131
+ )
1023
1132
 
1024
1133
  return BackupRestoreResponse(
1025
1134
  backup_id=payload.backup_id,
@@ -1027,7 +1136,7 @@ def import_backup(
1027
1136
  backup_filename=payload.backup_filename,
1028
1137
  backup_namespace=backup_namespace,
1029
1138
  target_namespace=target_namespace,
1030
- active_namespace_restarted=False,
1031
- open_namespace_suggested=True,
1139
+ active_namespace_restarted=target_is_active,
1140
+ open_namespace_suggested=not target_is_active,
1032
1141
  message="Backup imported successfully",
1033
1142
  )
@@ -34,8 +34,10 @@ from app.security.encryption import set_encryption_required
34
34
  from app.server_runtime import resolve_mcp_agent_public_origin
35
35
  from app.server_runtime import resolve_https_redirect_url
36
36
  from app.server_runtime import resolve_request_host_for_https_redirect
37
+ from app.server_runtime import validate_namespace
37
38
  from app.services.exception_capture import CapturedExceptionContext
38
39
  from app.services.namespace_deletion_jobs import load_namespace_deletion_job
40
+ from app.services.namespace_rename_jobs import load_namespace_rename_job
39
41
  from app.services.namespace_switcher import build_namespace_catalog
40
42
  from app.services.namespace_switcher import open_or_launch_namespace
41
43
  from app.services.diagnostics import configure_process_diagnostics
@@ -435,36 +437,17 @@ async def maintenance_page(request: Request):
435
437
  )
436
438
 
437
439
 
438
- def _build_namespace_deleted_links(*, job_id: str, deleted_namespace: str) -> list[dict[str, str]]:
439
- catalog = build_namespace_catalog(
440
- environ=os.environ,
441
- current_namespace=ACTIVE_NAMESPACE,
442
- )
443
- raw_namespaces = catalog["namespaces"]
444
- if not isinstance(raw_namespaces, list):
445
- raise RuntimeError("Namespace catalog missing namespaces")
446
- links: list[dict[str, str]] = []
447
- for entry in raw_namespaces:
448
- if not isinstance(entry, dict):
449
- raise RuntimeError("Namespace catalog entry must be an object")
450
- namespace = entry["namespace"]
451
- if not isinstance(namespace, str) or namespace == "":
452
- raise RuntimeError("Namespace catalog entry missing namespace")
453
- if namespace == deleted_namespace:
454
- continue
455
- query = urlencode({"job": job_id, "namespace": namespace})
456
- link_meta = "Open namespace"
457
- if namespace == ACTIVE_NAMESPACE:
458
- link_meta = "Already running"
459
- links.append(
460
- {
461
- "namespace": namespace,
462
- "label": namespace,
463
- "meta": link_meta,
464
- "href": f"/namespace-deleted/open?{query}",
465
- }
466
- )
467
- return links
440
+ def _build_namespace_deleted_links(*, job_id: str, redirect_namespace: str) -> list[dict[str, str]]:
441
+ normalized_redirect_namespace = validate_namespace(namespace=redirect_namespace)
442
+ query = urlencode({"job": job_id, "namespace": normalized_redirect_namespace})
443
+ return [
444
+ {
445
+ "namespace": normalized_redirect_namespace,
446
+ "label": normalized_redirect_namespace,
447
+ "meta": "Selected destination",
448
+ "href": f"/namespace-deleted/open?{query}",
449
+ }
450
+ ]
468
451
 
469
452
 
470
453
  def _resolve_catalog_profile(*, namespace: str) -> tuple[int, int | None, int]:
@@ -543,7 +526,7 @@ async def namespace_deleted_page(request: Request):
543
526
  template = templates.get_template("namespace_deleted.html")
544
527
  namespace_links = _build_namespace_deleted_links(
545
528
  job_id=job_record["job_id"],
546
- deleted_namespace=job_record["deleted_namespace"],
529
+ redirect_namespace=job_record["redirect_namespace"],
547
530
  )
548
531
  page_state = json.dumps(
549
532
  {
@@ -551,6 +534,7 @@ async def namespace_deleted_page(request: Request):
551
534
  "deletedNamespace": job_record["deleted_namespace"],
552
535
  "initialStatus": job_record["status"],
553
536
  "initialError": job_record["error"],
537
+ "redirectNamespace": job_record["redirect_namespace"],
554
538
  }
555
539
  )
556
540
  return template.render(
@@ -588,7 +572,10 @@ async def namespace_deleted_open_page(request: Request):
588
572
  return HTMLResponse(f"Namespace deletion job not found: {job_id}", status_code=404)
589
573
  if job_record["status"] == "pending":
590
574
  return HTMLResponse("Namespace deletion is still in progress", status_code=409)
591
- if namespace == job_record["deleted_namespace"]:
575
+ if (
576
+ namespace == job_record["deleted_namespace"]
577
+ and namespace != job_record["redirect_namespace"]
578
+ ):
592
579
  return HTMLResponse("Deleted namespace is unavailable", status_code=400)
593
580
 
594
581
  launch_capture = CapturedExceptionContext(
@@ -618,6 +605,75 @@ async def namespace_deleted_open_page(request: Request):
618
605
  return response
619
606
 
620
607
 
608
+ @app.get("/namespace-renamed", response_class=HTMLResponse)
609
+ async def namespace_renamed_page(request: Request):
610
+ job_id = request.query_params.get("job")
611
+ if not isinstance(job_id, str) or job_id.strip() == "":
612
+ return HTMLResponse("Missing namespace rename job", status_code=400)
613
+ job_record_capture = CapturedExceptionContext(
614
+ RuntimeError,
615
+ TypeError,
616
+ ValueError,
617
+ FileNotFoundError,
618
+ )
619
+ job_record: dict[str, object] | None = None
620
+ with job_record_capture:
621
+ job_record = load_namespace_rename_job(job_id=job_id)
622
+ if job_record_capture.captured_exception is not None:
623
+ exc = job_record_capture.captured_exception
624
+ return HTMLResponse(str(exc), status_code=400)
625
+ if job_record is None:
626
+ return HTMLResponse(f"Namespace rename job not found: {job_id}", status_code=404)
627
+
628
+ page_state = json.dumps(
629
+ {
630
+ "jobId": job_record["job_id"],
631
+ "sourceNamespace": job_record["source_namespace"],
632
+ "targetNamespace": job_record["target_namespace"],
633
+ "initialStatus": job_record["status"],
634
+ "initialError": job_record["error"],
635
+ }
636
+ )
637
+ template = templates.get_template("namespace_renamed.html")
638
+ return template.render(
639
+ request=request,
640
+ version=VERSION,
641
+ asset_version=ASSET_VERSION,
642
+ page_title="MetaList - Namespace Renamed",
643
+ source_namespace=job_record["source_namespace"],
644
+ target_namespace=job_record["target_namespace"],
645
+ page_state_json=page_state,
646
+ )
647
+
648
+
649
+ @app.get("/namespace-renamed/open")
650
+ async def namespace_renamed_open_page(request: Request):
651
+ job_id = request.query_params.get("job")
652
+ if not isinstance(job_id, str) or job_id.strip() == "":
653
+ return HTMLResponse("Missing namespace rename job", status_code=400)
654
+ job_record_capture = CapturedExceptionContext(
655
+ RuntimeError,
656
+ TypeError,
657
+ ValueError,
658
+ FileNotFoundError,
659
+ )
660
+ job_record: dict[str, object] | None = None
661
+ with job_record_capture:
662
+ job_record = load_namespace_rename_job(job_id=job_id)
663
+ if job_record_capture.captured_exception is not None:
664
+ exc = job_record_capture.captured_exception
665
+ return HTMLResponse(str(exc), status_code=400)
666
+ if job_record is None:
667
+ return HTMLResponse(f"Namespace rename job not found: {job_id}", status_code=404)
668
+ if job_record["status"] != "succeeded":
669
+ return HTMLResponse("Namespace rename is not complete", status_code=409)
670
+ if ACTIVE_NAMESPACE != job_record["target_namespace"]:
671
+ return HTMLResponse("Renamed namespace is not running here", status_code=409)
672
+ response = RedirectResponse(url="/?force_reauth=1", status_code=307)
673
+ clear_auth_cookie(response=response)
674
+ return response
675
+
676
+
621
677
  @app.get("/locked", response_class=HTMLResponse)
622
678
  async def locked_page(request: Request, db: Annotated[SafeSession, Depends(get_db)]):
623
679
  """Render a dedicated locked screen when a session is invalidated."""
@@ -32,6 +32,7 @@ _DEFAULT_KEY_PATH = _DEFAULT_DATABASE_DIRECTORY / "certs" / "metalist-key.pem"
32
32
  _DEFAULT_RUNTIME_DIRECTORY = Path(tempfile.gettempdir()) / "metalist-runtime"
33
33
  _DEFAULT_NAMESPACES_DIRECTORY_NAME = "namespaces"
34
34
  _DEFAULT_NAMESPACE_DELETE_JOBS_DIRECTORY_NAME = "namespace-delete-jobs"
35
+ _DEFAULT_NAMESPACE_RENAME_JOBS_DIRECTORY_NAME = "namespace-rename-jobs"
35
36
  _DEFAULT_LOGS_DIRECTORY_NAME = "logs"
36
37
  _DEFAULT_NAMESPACE = "default"
37
38
  _DEFAULT_HTTP_PORT = 8000
@@ -126,6 +127,10 @@ def resolve_namespace_delete_jobs_directory() -> Path:
126
127
  return _DEFAULT_RUNTIME_DIRECTORY / _DEFAULT_NAMESPACE_DELETE_JOBS_DIRECTORY_NAME
127
128
 
128
129
 
130
+ def resolve_namespace_rename_jobs_directory() -> Path:
131
+ return _DEFAULT_RUNTIME_DIRECTORY / _DEFAULT_NAMESPACE_RENAME_JOBS_DIRECTORY_NAME
132
+
133
+
129
134
  def resolve_runtime_logs_directory() -> Path:
130
135
  return _DEFAULT_DATABASE_DIRECTORY / _DEFAULT_LOGS_DIRECTORY_NAME
131
136