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