MetaList 0.3.6__tar.gz → 0.3.8__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.6 → metalist-0.3.8}/MANIFEST.in +1 -0
  2. {metalist-0.3.6/metalist.egg-info → metalist-0.3.8}/PKG-INFO +1 -1
  3. {metalist-0.3.6 → metalist-0.3.8}/app/api/middleware/auth.py +2 -0
  4. {metalist-0.3.6 → metalist-0.3.8}/app/api/routes/auth.py +90 -1
  5. {metalist-0.3.6 → metalist-0.3.8}/app/api/routes/backups.py +56 -12
  6. {metalist-0.3.6 → metalist-0.3.8}/app/main.py +88 -32
  7. {metalist-0.3.6 → metalist-0.3.8}/app/server_runtime.py +5 -0
  8. {metalist-0.3.6 → metalist-0.3.8}/app/services/namespace_deletion_worker.py +32 -3
  9. metalist-0.3.8/app/services/namespace_rename_jobs.py +115 -0
  10. metalist-0.3.8/app/services/namespace_rename_worker.py +160 -0
  11. {metalist-0.3.6 → metalist-0.3.8}/app/services/namespace_switcher.py +341 -26
  12. {metalist-0.3.6 → metalist-0.3.8}/app/static/config/command_palette_tags.json +4 -0
  13. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/command-palette/command-palette-controller.js +15 -0
  14. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/command-palette/endpoint-registry.js +7 -0
  15. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/config.js +2 -0
  16. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/delete-namespace-modal.js +53 -4
  17. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/delete-namespace-validation.js +10 -3
  18. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/namespace-modals.js +165 -0
  19. metalist-0.3.8/app/static/js/modules/modals/namespace-rename-validation.js +32 -0
  20. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/namespace-deleted.js +29 -3
  21. metalist-0.3.8/app/static/js/namespace-renamed.js +116 -0
  22. {metalist-0.3.6 → metalist-0.3.8}/app/templates/namespace_deleted.html +1 -1
  23. metalist-0.3.8/app/templates/namespace_renamed.html +67 -0
  24. {metalist-0.3.6 → metalist-0.3.8}/app/version.py +1 -1
  25. {metalist-0.3.6 → metalist-0.3.8}/main.py +23 -24
  26. {metalist-0.3.6 → metalist-0.3.8/metalist.egg-info}/PKG-INFO +1 -1
  27. {metalist-0.3.6 → metalist-0.3.8}/metalist.egg-info/SOURCES.txt +5 -0
  28. {metalist-0.3.6 → metalist-0.3.8}/pyproject.toml +2 -0
  29. {metalist-0.3.6 → metalist-0.3.8}/README.md +0 -0
  30. {metalist-0.3.6 → metalist-0.3.8}/app/__init__.py +0 -0
  31. {metalist-0.3.6 → metalist-0.3.8}/app/api/__init__.py +0 -0
  32. {metalist-0.3.6 → metalist-0.3.8}/app/api/deps.py +0 -0
  33. {metalist-0.3.6 → metalist-0.3.8}/app/api/dev.py +0 -0
  34. {metalist-0.3.6 → metalist-0.3.8}/app/api/request_auth.py +0 -0
  35. {metalist-0.3.6 → metalist-0.3.8}/app/api/routes/files.py +0 -0
  36. {metalist-0.3.6 → metalist-0.3.8}/app/api/routes/mcp.py +0 -0
  37. {metalist-0.3.6 → metalist-0.3.8}/app/api/routes/memory.py +0 -0
  38. {metalist-0.3.6 → metalist-0.3.8}/app/api/routes/notes.py +0 -0
  39. {metalist-0.3.6 → metalist-0.3.8}/app/api/routes/ontology.py +0 -0
  40. {metalist-0.3.6 → metalist-0.3.8}/app/api/routes/reminders.py +0 -0
  41. {metalist-0.3.6 → metalist-0.3.8}/app/api/routes/sounds.py +0 -0
  42. {metalist-0.3.6 → metalist-0.3.8}/app/api/routes/test.py +0 -0
  43. {metalist-0.3.6 → metalist-0.3.8}/app/api/transactions.py +0 -0
  44. {metalist-0.3.6 → metalist-0.3.8}/app/config.py +0 -0
  45. {metalist-0.3.6 → metalist-0.3.8}/app/db/__init__.py +0 -0
  46. {metalist-0.3.6 → metalist-0.3.8}/app/db/backup_settings_sql.py +0 -0
  47. {metalist-0.3.6 → metalist-0.3.8}/app/db/engine.py +0 -0
  48. {metalist-0.3.6 → metalist-0.3.8}/app/db/file_schema.py +0 -0
  49. {metalist-0.3.6 → metalist-0.3.8}/app/db/file_session.py +0 -0
  50. {metalist-0.3.6 → metalist-0.3.8}/app/db/files_sql.py +0 -0
  51. {metalist-0.3.6 → metalist-0.3.8}/app/db/link_titles_sql.py +0 -0
  52. {metalist-0.3.6 → metalist-0.3.8}/app/db/migrations.py +0 -0
  53. {metalist-0.3.6 → metalist-0.3.8}/app/db/notes_sql.py +0 -0
  54. {metalist-0.3.6 → metalist-0.3.8}/app/db/ontology_rules_sql.py +0 -0
  55. {metalist-0.3.6 → metalist-0.3.8}/app/db/reminders_sql.py +0 -0
  56. {metalist-0.3.6 → metalist-0.3.8}/app/db/schema.py +0 -0
  57. {metalist-0.3.6 → metalist-0.3.8}/app/db/search_history_sql.py +0 -0
  58. {metalist-0.3.6 → metalist-0.3.8}/app/db/session.py +0 -0
  59. {metalist-0.3.6 → metalist-0.3.8}/app/db/settings_sql.py +0 -0
  60. {metalist-0.3.6 → metalist-0.3.8}/app/db/sounds_sql.py +0 -0
  61. {metalist-0.3.6 → metalist-0.3.8}/app/db/tab_state_sql.py +0 -0
  62. {metalist-0.3.6 → metalist-0.3.8}/app/encryption_audit.py +0 -0
  63. {metalist-0.3.6 → metalist-0.3.8}/app/mcp/__init__.py +0 -0
  64. {metalist-0.3.6 → metalist-0.3.8}/app/mcp/__main__.py +0 -0
  65. {metalist-0.3.6 → metalist-0.3.8}/app/mcp/errors.py +0 -0
  66. {metalist-0.3.6 → metalist-0.3.8}/app/mcp/policy.py +0 -0
  67. {metalist-0.3.6 → metalist-0.3.8}/app/mcp/read_service.py +0 -0
  68. {metalist-0.3.6 → metalist-0.3.8}/app/mcp/server.py +0 -0
  69. {metalist-0.3.6 → metalist-0.3.8}/app/mcp/tools.py +0 -0
  70. {metalist-0.3.6 → metalist-0.3.8}/app/models/commands.py +0 -0
  71. {metalist-0.3.6 → metalist-0.3.8}/app/models/database.py +0 -0
  72. {metalist-0.3.6 → metalist-0.3.8}/app/models/entities.py +0 -0
  73. {metalist-0.3.6 → metalist-0.3.8}/app/models/enums.py +0 -0
  74. {metalist-0.3.6 → metalist-0.3.8}/app/models/linked_list.py +0 -0
  75. {metalist-0.3.6 → metalist-0.3.8}/app/models/list_operations.py +0 -0
  76. {metalist-0.3.6 → metalist-0.3.8}/app/models/list_traversal.py +0 -0
  77. {metalist-0.3.6 → metalist-0.3.8}/app/models/note_crud.py +0 -0
  78. {metalist-0.3.6 → metalist-0.3.8}/app/models/utils.py +0 -0
  79. {metalist-0.3.6 → metalist-0.3.8}/app/presentation/__init__.py +0 -0
  80. {metalist-0.3.6 → metalist-0.3.8}/app/presentation/render/__init__.py +0 -0
  81. {metalist-0.3.6 → metalist-0.3.8}/app/presentation/render/note_renderer.py +0 -0
  82. {metalist-0.3.6 → metalist-0.3.8}/app/presentation/templates.py +0 -0
  83. {metalist-0.3.6 → metalist-0.3.8}/app/security/encryption.py +0 -0
  84. {metalist-0.3.6 → metalist-0.3.8}/app/services/__init__.py +0 -0
  85. {metalist-0.3.6 → metalist-0.3.8}/app/services/auth.py +0 -0
  86. {metalist-0.3.6 → metalist-0.3.8}/app/services/auth_cache_state.py +0 -0
  87. {metalist-0.3.6 → metalist-0.3.8}/app/services/auth_service.py +0 -0
  88. {metalist-0.3.6 → metalist-0.3.8}/app/services/backlinks.py +0 -0
  89. {metalist-0.3.6 → metalist-0.3.8}/app/services/backup_service.py +0 -0
  90. {metalist-0.3.6 → metalist-0.3.8}/app/services/backup_settings_service.py +0 -0
  91. {metalist-0.3.6 → metalist-0.3.8}/app/services/base_service.py +0 -0
  92. {metalist-0.3.6 → metalist-0.3.8}/app/services/client_state_service.py +0 -0
  93. {metalist-0.3.6 → metalist-0.3.8}/app/services/content_cache.py +0 -0
  94. {metalist-0.3.6 → metalist-0.3.8}/app/services/content_formatting.py +0 -0
  95. {metalist-0.3.6 → metalist-0.3.8}/app/services/date_filtering.py +0 -0
  96. {metalist-0.3.6 → metalist-0.3.8}/app/services/dependencies.py +0 -0
  97. {metalist-0.3.6 → metalist-0.3.8}/app/services/diagnostics.py +0 -0
  98. {metalist-0.3.6 → metalist-0.3.8}/app/services/embedded_references.py +0 -0
  99. {metalist-0.3.6 → metalist-0.3.8}/app/services/encryption.py +0 -0
  100. {metalist-0.3.6 → metalist-0.3.8}/app/services/exception_capture.py +0 -0
  101. {metalist-0.3.6 → metalist-0.3.8}/app/services/file_registry.py +0 -0
  102. {metalist-0.3.6 → metalist-0.3.8}/app/services/file_storage.py +0 -0
  103. {metalist-0.3.6 → metalist-0.3.8}/app/services/html_export.py +0 -0
  104. {metalist-0.3.6 → metalist-0.3.8}/app/services/html_unformatting.py +0 -0
  105. {metalist-0.3.6 → metalist-0.3.8}/app/services/hydration_state.py +0 -0
  106. {metalist-0.3.6 → metalist-0.3.8}/app/services/integrity.py +0 -0
  107. {metalist-0.3.6 → metalist-0.3.8}/app/services/latex_rendering.py +0 -0
  108. {metalist-0.3.6 → metalist-0.3.8}/app/services/link_titles.py +0 -0
  109. {metalist-0.3.6 → metalist-0.3.8}/app/services/login_rate_limit.py +0 -0
  110. {metalist-0.3.6 → metalist-0.3.8}/app/services/maintenance_mode.py +0 -0
  111. {metalist-0.3.6 → metalist-0.3.8}/app/services/markdown_rendering.py +0 -0
  112. {metalist-0.3.6 → metalist-0.3.8}/app/services/memory.py +0 -0
  113. {metalist-0.3.6 → metalist-0.3.8}/app/services/memory_service.py +0 -0
  114. {metalist-0.3.6 → metalist-0.3.8}/app/services/namespace_deletion_jobs.py +0 -0
  115. {metalist-0.3.6 → metalist-0.3.8}/app/services/note_image_tags.py +0 -0
  116. {metalist-0.3.6 → metalist-0.3.8}/app/services/note_service.py +0 -0
  117. {metalist-0.3.6 → metalist-0.3.8}/app/services/note_store.py +0 -0
  118. {metalist-0.3.6 → metalist-0.3.8}/app/services/ontology_rules_store.py +0 -0
  119. {metalist-0.3.6 → metalist-0.3.8}/app/services/query_service.py +0 -0
  120. {metalist-0.3.6 → metalist-0.3.8}/app/services/reminders.py +0 -0
  121. {metalist-0.3.6 → metalist-0.3.8}/app/services/root_sorting.py +0 -0
  122. {metalist-0.3.6 → metalist-0.3.8}/app/services/runtime_hardening.py +0 -0
  123. {metalist-0.3.6 → metalist-0.3.8}/app/services/search_history.py +0 -0
  124. {metalist-0.3.6 → metalist-0.3.8}/app/services/search_index.py +0 -0
  125. {metalist-0.3.6 → metalist-0.3.8}/app/services/search_query.py +0 -0
  126. {metalist-0.3.6 → metalist-0.3.8}/app/services/search_text.py +0 -0
  127. {metalist-0.3.6 → metalist-0.3.8}/app/services/session_timeout_service.py +0 -0
  128. {metalist-0.3.6 → metalist-0.3.8}/app/services/shell_session_service.py +0 -0
  129. {metalist-0.3.6 → metalist-0.3.8}/app/services/snapshot.py +0 -0
  130. {metalist-0.3.6 → metalist-0.3.8}/app/services/sound_storage.py +0 -0
  131. {metalist-0.3.6 → metalist-0.3.8}/app/services/store.py +0 -0
  132. {metalist-0.3.6 → metalist-0.3.8}/app/services/sync.py +0 -0
  133. {metalist-0.3.6 → metalist-0.3.8}/app/services/sync_state.py +0 -0
  134. {metalist-0.3.6 → metalist-0.3.8}/app/services/tab_state.py +0 -0
  135. {metalist-0.3.6 → metalist-0.3.8}/app/services/tag_case.py +0 -0
  136. {metalist-0.3.6 → metalist-0.3.8}/app/services/tag_ontology.py +0 -0
  137. {metalist-0.3.6 → metalist-0.3.8}/app/services/tag_rename.py +0 -0
  138. {metalist-0.3.6 → metalist-0.3.8}/app/services/tag_suggestions.py +0 -0
  139. {metalist-0.3.6 → metalist-0.3.8}/app/services/tag_term_matching.py +0 -0
  140. {metalist-0.3.6 → metalist-0.3.8}/app/services/test_reset.py +0 -0
  141. {metalist-0.3.6 → metalist-0.3.8}/app/services/tokens.py +0 -0
  142. {metalist-0.3.6 → metalist-0.3.8}/app/services/transaction_manager.py +0 -0
  143. {metalist-0.3.6 → metalist-0.3.8}/app/services/undo_service.py +0 -0
  144. {metalist-0.3.6 → metalist-0.3.8}/app/services/undo_state.py +0 -0
  145. {metalist-0.3.6 → metalist-0.3.8}/app/services/view_cache.py +0 -0
  146. {metalist-0.3.6 → metalist-0.3.8}/app/services/view_diff.py +0 -0
  147. {metalist-0.3.6 → metalist-0.3.8}/app/services/view_state.py +0 -0
  148. {metalist-0.3.6 → metalist-0.3.8}/app/services/windows_process_control.py +0 -0
  149. {metalist-0.3.6 → metalist-0.3.8}/app/startup_js_sanity.py +0 -0
  150. {metalist-0.3.6 → metalist-0.3.8}/app/startup_sanity.py +0 -0
  151. {metalist-0.3.6 → metalist-0.3.8}/app/startup_sanity_config.py +0 -0
  152. {metalist-0.3.6 → metalist-0.3.8}/app/static/css/forms.css +0 -0
  153. {metalist-0.3.6 → metalist-0.3.8}/app/static/css/main.css +0 -0
  154. {metalist-0.3.6 → metalist-0.3.8}/app/static/img/favicon.ico +0 -0
  155. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/locked.js +0 -0
  156. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/main.js +0 -0
  157. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/activity-tracker.js +0 -0
  158. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/api-client.js +0 -0
  159. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/async-result.js +0 -0
  160. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/auth.js +0 -0
  161. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/client-state-api.js +0 -0
  162. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/client-state-migration.js +0 -0
  163. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
  164. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/command-palette/preferences-store.js +0 -0
  165. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/command-palette/tag-config-loader.js +0 -0
  166. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/command-palette/usage-store.js +0 -0
  167. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/connectivity-monitor.js +0 -0
  168. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/context-menu/context-menu-registry.js +0 -0
  169. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/context-menu/context-menu-service.js +0 -0
  170. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
  171. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/dom-utils-fixed.js +0 -0
  172. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/dom-utils.js +0 -0
  173. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/editor-commands.js +0 -0
  174. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/editor-selection.js +0 -0
  175. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/editor-toolbar.js +0 -0
  176. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/error-handler.js +0 -0
  177. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/error-overlay.js +0 -0
  178. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/location-flags.js +0 -0
  179. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/login-namespace-picker.js +0 -0
  180. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
  181. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/backup-restore-modal.js +0 -0
  182. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/backup-result-modal.js +0 -0
  183. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
  184. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/backup-settings-modal.js +0 -0
  185. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/base-modal.js +0 -0
  186. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/help-modal.js +0 -0
  187. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/memory-modal.js +0 -0
  188. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/namespace-loading-page.js +0 -0
  189. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/namespace-ports-profile.js +0 -0
  190. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/ontology-modal.js +0 -0
  191. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/password-modal.js +0 -0
  192. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/prioritize-modal.js +0 -0
  193. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/random-password-modal.js +0 -0
  194. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/reminder-modal.js +0 -0
  195. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
  196. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
  197. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/sound-manager-modal.js +0 -0
  198. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/modals/version-info-modal.js +0 -0
  199. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/actions/content-actions.js +0 -0
  200. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
  201. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/actions/note-actions.js +0 -0
  202. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
  203. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
  204. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/actions/ui-actions.js +0 -0
  205. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/events/context-menu-events.js +0 -0
  206. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/events/focus-events.js +0 -0
  207. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
  208. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
  209. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/events/keyboard-events.js +0 -0
  210. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/events/mouse-events.js +0 -0
  211. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
  212. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/mode-context.js +0 -0
  213. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/mode-logger.js +0 -0
  214. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
  215. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
  216. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
  217. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
  218. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
  219. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
  220. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -0
  221. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +0 -0
  222. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/date-filter-indicator-service.js +0 -0
  223. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/date-filter-service.js +0 -0
  224. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/differential-view-service.js +0 -0
  225. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
  226. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
  227. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
  228. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
  229. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
  230. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
  231. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +0 -0
  232. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +0 -0
  233. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +0 -0
  234. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
  235. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
  236. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
  237. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
  238. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
  239. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
  240. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
  241. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
  242. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
  243. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
  244. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
  245. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
  246. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/rhs-panel-service.js +0 -0
  247. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
  248. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
  249. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
  250. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
  251. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
  252. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
  253. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
  254. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
  255. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
  256. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
  257. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
  258. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +0 -0
  259. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
  260. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
  261. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
  262. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
  263. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -0
  264. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/tag-bar-service.js +0 -0
  265. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +0 -0
  266. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +0 -0
  267. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
  268. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
  269. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/password-generator.js +0 -0
  270. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/reminder-store.js +0 -0
  271. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/reminder-surface-service.js +0 -0
  272. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/selection-markers.js +0 -0
  273. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/session-auth.js +0 -0
  274. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/sound-service.js +0 -0
  275. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/tag-token.js +0 -0
  276. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/modules/uuid.js +0 -0
  277. {metalist-0.3.6 → metalist-0.3.8}/app/static/js/vendor/markdown-it.min.js +0 -0
  278. {metalist-0.3.6 → metalist-0.3.8}/app/static/video/login-startup.mp4 +0 -0
  279. {metalist-0.3.6 → metalist-0.3.8}/app/templates/base.html +0 -0
  280. {metalist-0.3.6 → metalist-0.3.8}/app/templates/index.html +0 -0
  281. {metalist-0.3.6 → metalist-0.3.8}/app/templates/locked.html +0 -0
  282. {metalist-0.3.6 → metalist-0.3.8}/app/templates/maintenance.html +0 -0
  283. {metalist-0.3.6 → metalist-0.3.8}/app/templates/notes_list.html +0 -0
  284. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/__init__.py +0 -0
  285. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/alphabetize_root_notes.py +0 -0
  286. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/base.py +0 -0
  287. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/collapse.py +0 -0
  288. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/copy_note.py +0 -0
  289. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/create_child.py +0 -0
  290. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/create_note.py +0 -0
  291. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/create_sibling.py +0 -0
  292. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/delete_subtree.py +0 -0
  293. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/expand.py +0 -0
  294. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/indent.py +0 -0
  295. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/lock.py +0 -0
  296. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/move.py +0 -0
  297. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/move_to_top.py +0 -0
  298. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/outdent.py +0 -0
  299. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/paste_child.py +0 -0
  300. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/paste_sibling.py +0 -0
  301. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/prioritize.py +0 -0
  302. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/record_edit_mode.py +0 -0
  303. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/redo.py +0 -0
  304. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/rename_tag.py +0 -0
  305. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/reset_updated_at.py +0 -0
  306. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/run_shell.py +0 -0
  307. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/search_comment_autofill.py +0 -0
  308. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/search_context_tags.py +0 -0
  309. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/set_collapse_bulk.py +0 -0
  310. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/set_collapse_in_context.py +0 -0
  311. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/split_note.py +0 -0
  312. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/toggle_reference_mode.py +0 -0
  313. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/toggle_todo_done.py +0 -0
  314. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/undo.py +0 -0
  315. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/unformat_content.py +0 -0
  316. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/update_content.py +0 -0
  317. {metalist-0.3.6 → metalist-0.3.8}/app/usecases/view.py +0 -0
  318. {metalist-0.3.6 → metalist-0.3.8}/app/utils/__init__.py +0 -0
  319. {metalist-0.3.6 → metalist-0.3.8}/app/utils/encryption.py +0 -0
  320. {metalist-0.3.6 → metalist-0.3.8}/app/utils/text_utils.py +0 -0
  321. {metalist-0.3.6 → metalist-0.3.8}/convert-from-legacy.py +0 -0
  322. {metalist-0.3.6 → metalist-0.3.8}/mcp_client.py +0 -0
  323. {metalist-0.3.6 → metalist-0.3.8}/metalist.egg-info/dependency_links.txt +0 -0
  324. {metalist-0.3.6 → metalist-0.3.8}/metalist.egg-info/entry_points.txt +0 -0
  325. {metalist-0.3.6 → metalist-0.3.8}/metalist.egg-info/requires.txt +0 -0
  326. {metalist-0.3.6 → metalist-0.3.8}/metalist.egg-info/top_level.txt +0 -0
  327. {metalist-0.3.6 → metalist-0.3.8}/scripts/generate-lan-cert.sh +0 -0
  328. {metalist-0.3.6 → metalist-0.3.8}/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.6
3
+ Version: 0.3.8
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
@@ -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",
@@ -47,8 +47,10 @@ from app.services.namespace_switcher import delete_current_namespace
47
47
  from app.services.namespace_switcher import delete_namespace
48
48
  from app.services.namespace_switcher import open_login_namespace
49
49
  from app.services.namespace_switcher import open_or_launch_namespace
50
+ from app.services.namespace_switcher import rename_current_namespace
50
51
  from app.services.namespace_switcher import save_namespace_port_profiles
51
52
  from app.services.namespace_deletion_jobs import load_namespace_deletion_job
53
+ from app.services.namespace_rename_jobs import load_namespace_rename_job
52
54
  from app.server_runtime import NamespaceLaunchProfile
53
55
  from app.server_runtime import resolve_namespace_directory
54
56
  from app.server_runtime import resolve_namespaced_database_path
@@ -590,6 +592,15 @@ def _require_string_field(payload: dict[str, object], field_name: str) -> str:
590
592
  return value
591
593
 
592
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
+
593
604
  def _require_int_field(payload: dict[str, object], field_name: str) -> int:
594
605
  if field_name not in payload:
595
606
  raise HTTPException(status_code=400, detail=f"{field_name} is required")
@@ -968,7 +979,8 @@ def save_namespace_ports(
968
979
  def _delete_namespace_from_body(*, body: dict[str, object], target_namespace: str) -> dict[str, object]:
969
980
  normalized_target_namespace = validate_namespace(namespace=target_namespace)
970
981
  confirmed_namespace = _require_string_field(body, "confirmed_namespace")
971
- 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")
972
984
 
973
985
  if _namespace_requires_password(namespace=normalized_target_namespace):
974
986
  _verify_namespace_password(
@@ -989,6 +1001,7 @@ def _delete_namespace_from_body(*, body: dict[str, object], target_namespace: st
989
1001
  current_namespace=ACTIVE_NAMESPACE,
990
1002
  target_namespace=normalized_target_namespace,
991
1003
  confirmed_namespace=confirmed_namespace,
1004
+ redirect_namespace=redirect_namespace,
992
1005
  )
993
1006
  if delete_capture.captured_exception is not None:
994
1007
  exc = delete_capture.captured_exception
@@ -1019,11 +1032,33 @@ def namespace_delete_preflight(
1019
1032
  target_requires_password = False
1020
1033
  if target_exists:
1021
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")
1022
1055
  return {
1023
1056
  "target_namespace": target_namespace,
1024
1057
  "target_exists": target_exists,
1025
1058
  "target_requires_password": target_requires_password,
1026
1059
  "is_current_namespace": target_namespace == ACTIVE_NAMESPACE,
1060
+ "redirect_namespaces": redirect_namespaces,
1061
+ "recreates_default": recreates_default,
1027
1062
  }
1028
1063
 
1029
1064
 
@@ -1054,6 +1089,41 @@ def delete_active_namespace(
1054
1089
  )
1055
1090
 
1056
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
+
1057
1127
  @router.get("/namespaces/delete-jobs/{job_id}")
1058
1128
  def namespace_delete_job_status(
1059
1129
  job_id: str,
@@ -1075,6 +1145,25 @@ def namespace_delete_job_status(
1075
1145
  return job_record
1076
1146
 
1077
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
+
1078
1167
  @router.post("/hydrate", response_model=HydrationStatusResponse)
1079
1168
  @transactional_route
1080
1169
  def hydrate_cache(
@@ -717,6 +717,32 @@ def _required_existing_target_launch_profile(*, target_namespace: str) -> Backup
717
717
  )
718
718
 
719
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
+
720
746
  def _backup_profile_from_request(
721
747
  *,
722
748
  namespace: str,
@@ -920,22 +946,22 @@ def restore_backup_preflight(
920
946
  target_exists = _restore_target_exists(target_namespace=target_namespace)
921
947
  suggested_profile = None
922
948
  port_conflicts: list[str] = []
923
- if target_namespace != backup_namespace:
924
- if target_exists:
925
- target_profile = _required_existing_target_launch_profile(
926
- target_namespace=target_namespace,
927
- )
928
- suggested_profile = _response_profile_from_backup_profile(profile=target_profile)
929
- assert suggested_profile is not None
930
- else:
949
+ if target_exists:
950
+ target_profile = _required_existing_target_launch_profile(
951
+ target_namespace=target_namespace,
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:
931
957
  port_conflicts = _collect_import_profile_port_conflicts(
932
958
  target_namespace=target_namespace,
933
959
  launch_profile=restored_profile,
934
960
  )
935
- suggested_profile = _suggest_import_launch_profile(
936
- target_namespace=target_namespace,
937
- restored_profile=restored_profile,
938
- )
961
+ suggested_profile = _suggest_import_launch_profile(
962
+ target_namespace=target_namespace,
963
+ restored_profile=restored_profile,
964
+ )
939
965
 
940
966
  return BackupRestorePreflightResponse(
941
967
  backup_namespace=backup_namespace,
@@ -983,10 +1009,22 @@ def restore_backup(
983
1009
  backup_path = folder_directory / folder_filename
984
1010
  if not backup_path.exists():
985
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
+ )
986
1018
  if target_namespace == ACTIVE_NAMESPACE:
987
1019
  maintenance_service.enter_maintenance("Restoring backup")
988
1020
  try:
989
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
+ )
990
1028
  _reset_runtime_state_after_restore()
991
1029
  finally:
992
1030
  maintenance_service.exit_maintenance()
@@ -995,6 +1033,12 @@ def restore_backup(
995
1033
  open_namespace_suggested = False
996
1034
  else:
997
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
+ )
998
1042
  return BackupRestoreResponse(
999
1043
  backup_id=payload.backup_id,
1000
1044
  source=payload.source,
@@ -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
 
@@ -10,11 +10,13 @@ import time
10
10
  import traceback
11
11
 
12
12
  import app.server_runtime as server_runtime
13
+ from app.server_runtime import save_namespace_launch_profile
13
14
  from app.server_runtime import resolve_namespace_directory
14
15
  from app.server_runtime import validate_namespace
15
16
  from app.services.exception_capture import CapturedExceptionContext
16
17
  from app.services.namespace_deletion_jobs import mark_namespace_deletion_job_failed
17
18
  from app.services.namespace_deletion_jobs import mark_namespace_deletion_job_succeeded
19
+ from app.services.namespace_switcher import open_or_launch_namespace
18
20
  from app.services.windows_process_control import is_process_running as is_windows_process_running
19
21
  from app.services.windows_process_control import stop_process as stop_windows_process
20
22
 
@@ -30,6 +32,11 @@ def _parse_args() -> argparse.Namespace:
30
32
  parser.add_argument("--pid", type=int, required=True)
31
33
  parser.add_argument("--namespace", required=True)
32
34
  parser.add_argument("--job-id", required=True)
35
+ parser.add_argument("--replacement-namespace", required=True)
36
+ parser.add_argument("--replacement-port", type=int, required=True)
37
+ parser.add_argument("--replacement-https-port", type=int, required=True)
38
+ parser.add_argument("--replacement-mcp-port", type=int, required=True)
39
+ parser.add_argument("--recreate-default", action="store_true")
33
40
  return parser.parse_args()
34
41
 
35
42
 
@@ -123,16 +130,38 @@ def _delete_namespace_directory(*, namespace: str) -> None:
123
130
  shutil.rmtree(namespace_directory)
124
131
 
125
132
 
133
+ def _recreate_default_namespace(*, args: argparse.Namespace) -> None:
134
+ replacement_namespace = validate_namespace(namespace=args.replacement_namespace)
135
+ if replacement_namespace != server_runtime._DEFAULT_NAMESPACE:
136
+ raise RuntimeError("Final namespace deletion must recreate default")
137
+ https_port = args.replacement_https_port
138
+ if https_port == 0:
139
+ https_port = None
140
+ save_namespace_launch_profile(
141
+ namespace=replacement_namespace,
142
+ port=args.replacement_port,
143
+ https_port=https_port,
144
+ mcp_port=args.replacement_mcp_port,
145
+ )
146
+ open_or_launch_namespace(
147
+ environ=os.environ,
148
+ current_namespace=None,
149
+ namespace=replacement_namespace,
150
+ port=args.replacement_port,
151
+ https_port=https_port,
152
+ mcp_port=args.replacement_mcp_port,
153
+ )
154
+
155
+
126
156
  def main() -> None:
127
157
  args = _parse_args()
128
158
  main_capture = CapturedExceptionContext(Exception)
129
159
  with main_capture:
130
160
  normalized_namespace = validate_namespace(namespace=args.namespace)
131
- if normalized_namespace == server_runtime._DEFAULT_NAMESPACE:
132
- raise RuntimeError("Default namespace cannot be deleted")
133
-
134
161
  _stop_process(pid=args.pid)
135
162
  _delete_namespace_directory(namespace=normalized_namespace)
163
+ if args.recreate_default:
164
+ _recreate_default_namespace(args=args)
136
165
  mark_namespace_deletion_job_succeeded(job_id=args.job_id)
137
166
  if main_capture.captured_exception is not None:
138
167
  mark_namespace_deletion_job_failed(
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ import re
6
+ import uuid
7
+
8
+ from app.server_runtime import resolve_namespace_rename_jobs_directory
9
+
10
+
11
+ _JOB_ID_PATTERN = re.compile(r"^[a-f0-9-]{36}$")
12
+
13
+
14
+ def create_namespace_rename_job(
15
+ *,
16
+ source_namespace: str,
17
+ target_namespace: str,
18
+ ) -> dict[str, str]:
19
+ if not isinstance(source_namespace, str) or source_namespace == "":
20
+ raise TypeError("source_namespace must be a non-empty string")
21
+ if not isinstance(target_namespace, str) or target_namespace == "":
22
+ raise TypeError("target_namespace must be a non-empty string")
23
+
24
+ job_id = str(uuid.uuid4())
25
+ job_record = {
26
+ "job_id": job_id,
27
+ "status": "pending",
28
+ "source_namespace": source_namespace,
29
+ "target_namespace": target_namespace,
30
+ "error": "",
31
+ }
32
+ _write_job_record(job_id=job_id, job_record=job_record)
33
+ return job_record
34
+
35
+
36
+ def load_namespace_rename_job(*, job_id: str) -> dict[str, str] | None:
37
+ normalized_job_id = _validate_job_id(job_id=job_id)
38
+ job_path = _resolve_job_path(job_id=normalized_job_id)
39
+ if not job_path.is_file():
40
+ return None
41
+ payload = json.loads(job_path.read_text(encoding="utf-8"))
42
+ if not isinstance(payload, dict):
43
+ raise RuntimeError(f"Namespace rename job is not an object: {job_path}")
44
+ return _coerce_job_record(payload=payload, job_id=normalized_job_id)
45
+
46
+
47
+ def mark_namespace_rename_job_succeeded(*, job_id: str) -> None:
48
+ job_record = _require_existing_job(job_id=job_id)
49
+ job_record["status"] = "succeeded"
50
+ job_record["error"] = ""
51
+ _write_job_record(job_id=job_id, job_record=job_record)
52
+
53
+
54
+ def mark_namespace_rename_job_failed(*, job_id: str, error: str) -> None:
55
+ if not isinstance(error, str) or error.strip() == "":
56
+ raise TypeError("error must be a non-empty string")
57
+ job_record = _require_existing_job(job_id=job_id)
58
+ job_record["status"] = "failed"
59
+ job_record["error"] = error.strip()
60
+ _write_job_record(job_id=job_id, job_record=job_record)
61
+
62
+
63
+ def _require_existing_job(*, job_id: str) -> dict[str, str]:
64
+ job_record = load_namespace_rename_job(job_id=job_id)
65
+ if job_record is None:
66
+ raise RuntimeError(f"Namespace rename job not found: {job_id}")
67
+ return job_record
68
+
69
+
70
+ def _validate_job_id(*, job_id: str) -> str:
71
+ if not isinstance(job_id, str):
72
+ raise TypeError(f"job_id must be a string, got {type(job_id)}")
73
+ normalized_job_id = job_id.strip()
74
+ if _JOB_ID_PATTERN.fullmatch(normalized_job_id) is None:
75
+ raise RuntimeError(f"Invalid namespace rename job id: {job_id!r}")
76
+ return normalized_job_id
77
+
78
+
79
+ def _resolve_job_path(*, job_id: str) -> Path:
80
+ jobs_directory = resolve_namespace_rename_jobs_directory()
81
+ jobs_directory.mkdir(parents=True, exist_ok=True)
82
+ return jobs_directory / f"{job_id}.json"
83
+
84
+
85
+ def _coerce_job_record(*, payload: dict[str, object], job_id: str) -> dict[str, str]:
86
+ required_fields = ("status", "source_namespace", "target_namespace", "error")
87
+ for field_name in required_fields:
88
+ if field_name not in payload:
89
+ raise RuntimeError(f"Namespace rename job is missing {field_name}: {payload}")
90
+ status = payload["status"]
91
+ source_namespace = payload["source_namespace"]
92
+ target_namespace = payload["target_namespace"]
93
+ error = payload["error"]
94
+ if status not in {"pending", "succeeded", "failed"}:
95
+ raise RuntimeError(f"Namespace rename job has invalid status: {payload}")
96
+ if not isinstance(source_namespace, str) or source_namespace == "":
97
+ raise RuntimeError(f"Namespace rename job is missing source_namespace: {payload}")
98
+ if not isinstance(target_namespace, str) or target_namespace == "":
99
+ raise RuntimeError(f"Namespace rename job is missing target_namespace: {payload}")
100
+ if not isinstance(error, str):
101
+ raise RuntimeError(f"Namespace rename job has invalid error field: {payload}")
102
+ return {
103
+ "job_id": job_id,
104
+ "status": status,
105
+ "source_namespace": source_namespace,
106
+ "target_namespace": target_namespace,
107
+ "error": error,
108
+ }
109
+
110
+
111
+ def _write_job_record(*, job_id: str, job_record: dict[str, str]) -> None:
112
+ normalized_job_id = _validate_job_id(job_id=job_id)
113
+ serialized = json.dumps(job_record, indent=2, sort_keys=True)
114
+ job_path = _resolve_job_path(job_id=normalized_job_id)
115
+ job_path.write_text(serialized, encoding="utf-8")