MetaList 0.6.2__tar.gz → 0.6.3__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.6.2 → metalist-0.6.3}/PKG-INFO +14 -3
- {metalist-0.6.2 → metalist-0.6.3}/README.md +13 -2
- {metalist-0.6.2 → metalist-0.6.3}/app/api/middleware/auth.py +2 -0
- metalist-0.6.3/app/api/routes/app_updates.py +55 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/exception_boundaries.py +7 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/https_proxy.py +9 -3
- {metalist-0.6.2 → metalist-0.6.3}/app/main.py +2 -0
- metalist-0.6.3/app/services/app_update_worker.py +42 -0
- metalist-0.6.3/app/services/app_updates.py +190 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/client_state_service.py +5 -1
- {metalist-0.6.2 → metalist-0.6.3}/app/services/self_update.py +6 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/shell_session_service.py +1 -1
- {metalist-0.6.2 → metalist-0.6.3}/app/services/update_preflight.py +5 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/windows_process_control.py +37 -4
- {metalist-0.6.2 → metalist-0.6.3}/app/startup_sanity_config.py +3 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/config/command_palette_tags.json +1 -1
- {metalist-0.6.2 → metalist-0.6.3}/app/static/css/main.css +16 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/main.js +3 -0
- metalist-0.6.3/app/static/js/modules/app-update-service.js +106 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/auth.js +1 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/command-palette/command-palette-controller.js +5 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/config.js +1 -0
- metalist-0.6.3/app/static/js/modules/modals/version-info-modal.js +313 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/version.py +1 -1
- {metalist-0.6.2 → metalist-0.6.3}/metalist.egg-info/PKG-INFO +14 -3
- {metalist-0.6.2 → metalist-0.6.3}/metalist.egg-info/SOURCES.txt +4 -0
- metalist-0.6.2/app/static/js/modules/modals/version-info-modal.js +0 -190
- {metalist-0.6.2 → metalist-0.6.3}/MANIFEST.in +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/__init__.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/__init__.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/deps.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/dev.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/note_requests.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/request_auth.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/routes/ai.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/routes/auth.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/routes/backups.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/routes/files.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/routes/notes.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/routes/ontology.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/routes/reminders.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/routes/remote_images.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/routes/test.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/transactions.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/api/upload_limits.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/config.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/data_directory.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/__init__.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/backup_settings_sql.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/engine.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/file_schema.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/file_session.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/files_sql.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/link_titles_sql.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/live_recovery.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/migrations.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/notes_sql.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/ontology_rules_sql.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/reminders_sql.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/retire_sounds.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/schema.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/search_history_sql.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/session.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/settings_sql.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/tab_state_sql.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/db/version.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/encryption_audit.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/models/commands.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/models/database.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/models/entities.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/models/enums.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/models/linked_list.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/models/list_operations.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/models/list_traversal.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/models/note_crud.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/models/utils.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/presentation/__init__.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/presentation/render/__init__.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/presentation/render/note_renderer.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/presentation/templates.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/security/authenticated_logging.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/security/encryption.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/security/http_headers.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/security/note_html.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/security/request_boundary.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/security/sensitive_cache.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/security/sensitive_logging.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/security/shell_execution.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/security/validation_errors.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/server_runtime.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/__init__.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/__init__.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/actions.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/cloud_privacy.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/context.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/evidence_serialization.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/inference.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/investigation.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/model_policy.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/ollama_inference.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/openai_cost_tracking.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/openai_inference.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/permissions.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/prompt_settings.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/prompts/__init__.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/prompts/final-response.md +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/prompts/system.md +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/prompts/tool-result.md +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/retrieval_settings.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/runtime.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/scope.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/skill_settings.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/skills/__init__.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/skills/scoped-investigation.md +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/tagging.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/tagging_run.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/token_estimation.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/tools.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/agent/trace.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/ai_chat.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/ai_chat_rendering.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/ai_chat_stream.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/auth.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/auth_cache_state.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/auth_service.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/backlink_index.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/backlinks.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/backup_service.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/backup_settings_service.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/bulk_operation.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/content_cache.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/content_formatting.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/diagnostics.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/embedded_references.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/encryption.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/exception_capture.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/file_registry.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/file_storage.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/footnote_layout.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/footnote_rendering.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/hierarchy.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/html_export.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/html_unformatting.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/hydration_state.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/image_size_formatting.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/inline_image_occurrences.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/input_errors.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/integrity.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/latex_rendering.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/link_titles.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/login_rate_limit.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/maintenance_mode.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/managed_ollama_runtime.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/markdown_rendering.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/namespace_deletion_jobs.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/namespace_deletion_worker.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/namespace_rename_jobs.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/namespace_rename_worker.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/namespace_runtime_guard.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/namespace_switcher.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/note_fullscreen.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/note_image_tags.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/note_ordering.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/note_store.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/ollama_provider.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/ontology_rules_store.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/openai_credentials.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/password_note_fields.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/reference_presentation.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/reminders.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/remote_image_proxy.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/request_recovery.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/resource_limits.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/root_sorting.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/runtime_generation.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/runtime_hardening.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/runtime_lock.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/search_history.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/search_history_storage.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/search_index.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/search_query.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/search_text.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/selected_text_tag.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/session_timeout_service.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/snapshot.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/store.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/structured_note_renderers.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/sync.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/sync_state.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/tab_state.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/tag_case.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/tag_ontology.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/tag_rename.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/tag_suggestions.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/tag_term_matching.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/test_reset.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/tokens.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/undo_state.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/update_backups.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/view_cache.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/view_diff.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/services/view_state.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/startup_environment.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/startup_js_sanity.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/startup_sanity.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/css/floating-notes.css +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/css/forms.css +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/img/favicon.ico +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/locked.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/activity-tracker.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/ai-chat/agent-prompt-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/ai-chat/agent-retrieval-settings.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/ai-chat/ai-agent-debug-view.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/ai-chat/ai-chat-api.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/ai-chat/ai-chat-panel-controller.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/ai-chat/ai-chat-panel-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/ai-chat/ai-thinking-level-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/ai-chat/bulk-elapsed-clock.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/ai-chat/bulk-proposal-ui.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/ai-chat/cloud-privacy-policy.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/ai-chat/proposal-menu.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/api-client.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/api-failure-classification-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/application-state.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/async-result.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/client-state-api.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/client-state-migration.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/command-palette/activation-auth-policy.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/command-palette/backup-command-availability.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/command-palette/endpoint-registry.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/command-palette/note-layout-preferences.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/command-palette/preferences-store.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/command-palette/tag-config-loader.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/command-palette/usage-store.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/connectivity-monitor.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/context-menu/context-menu-registry.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/context-menu/context-menu-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/context-menu/context-menu-target-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/disconnect-editing-state-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/dom-utils-fixed.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/dom-utils.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/editor-commands.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/editor-selection.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/editor-toolbar.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/elapsed-time.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/error-handler.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/error-overlay.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/expected-errors.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/location-flags.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/login-namespace-picker.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/maintenance.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/agent-prompt-editor-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/ai-agent-settings-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/backup-restore-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/backup-result-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/backup-settings-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/base-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/confirmation-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/delete-namespace-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/delete-namespace-validation.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/help-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/namespace-loading-page.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/namespace-modals.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/namespace-ports-profile.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/namespace-rename-validation.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/note-layout-appearance-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/ontology-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/password-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/prioritize-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/random-password-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/reminder-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/search-suggestion-statistics-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/actions/content-actions.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/actions/note-actions.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/actions/ui-actions.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/events/context-menu-events.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/events/focus-events.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/events/keyboard-events.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/events/mouse-events.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/mode-context.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/mode-logger.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/add-style-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/backlinks-navigation-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/deselect-selection-state-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/differential-view-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/file-drop-policy-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/floating-note-dom.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/floating-note-layout.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/floating-note-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/footnote-navigation-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/history-selection-policy-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/history-shortcut-policy-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/link-title-prefetch-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/mermaid-render-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/note-delete-shortcut-policy-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/note-fullscreen-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/note-timestamp-hover-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/reference-source-navigation-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/remote-image-proxy-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/remove-formatting-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/search-suggestion-statistics-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/search-suggestion-window-labels-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/search-suggestion-windows-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/selected-formatting-removal-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/selected-text-tag-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/tab-drag-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/tag-bar-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/tag-input-selection-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/tag-proposal-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/untagged-view-indicator-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/note-html-sanitizer.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/password-generator.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/password-operation-response.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/password-policy.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/password-strength.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/reminder-store.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/reminder-surface-service.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/selection-markers.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/session-auth.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/tag-token.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/modules/uuid.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/namespace-deleted.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/namespace-renamed.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/vendor/dompurify-3.4.13.LICENSE.txt +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/vendor/dompurify-3.4.13.min.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/vendor/markdown-it-14.3.2.LICENSE.txt +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/vendor/markdown-it-14.3.2.min.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/vendor/mermaid-11.16.1.LICENSE.txt +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/vendor/mermaid-11.16.1.min.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/vendor/zxcvbn-4.4.2.LICENSE.txt +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/js/vendor/zxcvbn-4.4.2.js +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/note-html-policy.json +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/static/video/login-startup.mp4 +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/templates/base.html +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/templates/index.html +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/templates/locked.html +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/templates/maintenance.html +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/templates/namespace_deleted.html +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/templates/namespace_renamed.html +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/templates/notes_list.html +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/upload_limits.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/__init__.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/add_selected_text_tag.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/alphabetize_root_notes.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/base.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/bulk_tag_proposals.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/collapse.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/copy_note.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/create_child.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/create_note.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/create_sibling.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/delete_subtree.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/delete_tag.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/expand.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/indent.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/lock.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/move.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/move_to_top.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/outdent.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/paste_child.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/paste_sibling.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/prefetch_link_titles.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/prioritize.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/redo.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/rename_tag.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/reset_updated_at.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/resize_image.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/run_shell.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/search_comment_autofill.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/search_context_tags.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/set_collapse_bulk.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/set_collapse_in_context.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/split_note.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/tag_proposals.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/toggle_reference_mode.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/toggle_todo_done.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/undo.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/unformat_content.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/update_content.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/usecases/view.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/utils/__init__.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/utils/encryption.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/app/utils/text_utils.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/convert-from-legacy.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/main.py +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/metalist.egg-info/dependency_links.txt +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/metalist.egg-info/entry_points.txt +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/metalist.egg-info/requires.txt +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/metalist.egg-info/top_level.txt +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/pyproject.toml +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/scripts/generate-lan-cert.sh +0 -0
- {metalist-0.6.2 → metalist-0.6.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metalist
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
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
|
|
@@ -48,6 +48,13 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
|
|
|
48
48
|
- Multi-tab search contexts with server-persisted scroll/search state (survives browser restarts)
|
|
49
49
|
- Manual namespace backups/restores to a user-selected backup folder with retention controls
|
|
50
50
|
|
|
51
|
+
## Changes in 0.6.3
|
|
52
|
+
|
|
53
|
+
- Restored HTTPS connection reuse and aligned the pending accept queue with the existing worker capacity, so a browser’s six-connection startup burst is admitted.
|
|
54
|
+
- Every page load or refresh checks PyPI asynchronously. A newly discovered release shows a dismissible notice linking to Version Info; typing `update` in the menu also finds it.
|
|
55
|
+
- Managed uv installations can update from Version Info, using the existing preflight, verified immutable backups, and namespace restart flow. Source checkouts and unmanaged installations explain why in-app installation is unavailable.
|
|
56
|
+
- Release validation now transfers every installed startup asset through concurrent persistent HTTP/HTTPS connections on all supported platforms and Python versions. Actual Edge startup over LAN-style HTTPS is also required on Windows before publication.
|
|
57
|
+
|
|
51
58
|
## Changes in 0.6.2
|
|
52
59
|
|
|
53
60
|
- Namespace startup allows two minutes, with progress every five seconds, instead of failing after 12 seconds on a busy machine. `METALIST_STARTUP_TIMEOUT_SECONDS` can increase the allowance. Failed children are stopped and reaped.
|
|
@@ -122,7 +129,11 @@ uv tool install metalist
|
|
|
122
129
|
metalist
|
|
123
130
|
```
|
|
124
131
|
|
|
125
|
-
|
|
132
|
+
For a persistent uv installation, you can update from inside MetaList: type **update** in the menu, open **Version Info**, and choose **Update to …** when a newer release is available. Each browser load or refresh asks the server to check PyPI asynchronously. A reminder-style notice links to Version Info the first time a newer version is discovered; the announced version is remembered in that namespace's client preferences. A slow or failed check does not block the app. Version Info can retry a failed check.
|
|
133
|
+
|
|
134
|
+
The in-app updater checks the selected release, creates verified backups, and restarts every namespace. Keep Version Info open to follow progress and reload when ready; closing and reopening it in the same page resumes progress. Signing in again may be necessary. Source checkouts and unmanaged installations still check for releases, but show why in-app installation is unavailable. Update job records and logs live under `~/MetaList/update-jobs/` (or the configured data directory). If installation/restart fails after servers stop, inspect the updater output and run `metalist` on the server to restart it; there is no automatic rollback.
|
|
135
|
+
|
|
136
|
+
You can also update and restart MetaList with one cross-platform command:
|
|
126
137
|
```bash
|
|
127
138
|
metalist update
|
|
128
139
|
```
|
|
@@ -130,7 +141,7 @@ The updater checks the installed version against the latest PyPI release first.
|
|
|
130
141
|
|
|
131
142
|
Backups are saved to `~/MetaList/namespaces/<namespace>/backups/<namespace>-<timestamp>.metalist-backup.tar.gz`; the updater prints each verified path. Existing backups remain unchanged and are not pruned. If any backup fails, the update aborts with the current installation intact; run `metalist` to restart the stopped servers after resolving the failure.
|
|
132
143
|
|
|
133
|
-
Only after all backups pass does the updater hand off to an external PowerShell process on Windows or `/bin/sh` on macOS/Linux so the installed environment can unlock. It delegates installation to uv, pinning the current base interpreter and the exact tested package/dependency versions, using the cache populated during preflight with network access disabled. It then launches MetaList again, and reports the installed version (for example, `MetaList updated to v0.5.0.`). This protection requires an installed version containing the backup safeguard and applies to `metalist update
|
|
144
|
+
Only after all backups pass does the updater hand off to an external PowerShell process on Windows or `/bin/sh` on macOS/Linux so the installed environment can unlock. It delegates installation to uv, pinning the current base interpreter and the exact tested package/dependency versions, using the cache populated during preflight with network access disabled. It then launches MetaList again, and reports the installed version (for example, `MetaList updated to v0.5.0.`). This protection requires an installed version containing the backup safeguard and applies to `metalist update` and the in-app update action; direct pip/uv install commands do not run it.
|
|
134
145
|
|
|
135
146
|
For pip, users can run `pip install metalist`. For a non-editable local install from this checkout, use `uv pip install .` or `pip install .` instead of the editable command below.
|
|
136
147
|
|
|
@@ -12,6 +12,13 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
|
|
|
12
12
|
- Multi-tab search contexts with server-persisted scroll/search state (survives browser restarts)
|
|
13
13
|
- Manual namespace backups/restores to a user-selected backup folder with retention controls
|
|
14
14
|
|
|
15
|
+
## Changes in 0.6.3
|
|
16
|
+
|
|
17
|
+
- Restored HTTPS connection reuse and aligned the pending accept queue with the existing worker capacity, so a browser’s six-connection startup burst is admitted.
|
|
18
|
+
- Every page load or refresh checks PyPI asynchronously. A newly discovered release shows a dismissible notice linking to Version Info; typing `update` in the menu also finds it.
|
|
19
|
+
- Managed uv installations can update from Version Info, using the existing preflight, verified immutable backups, and namespace restart flow. Source checkouts and unmanaged installations explain why in-app installation is unavailable.
|
|
20
|
+
- Release validation now transfers every installed startup asset through concurrent persistent HTTP/HTTPS connections on all supported platforms and Python versions. Actual Edge startup over LAN-style HTTPS is also required on Windows before publication.
|
|
21
|
+
|
|
15
22
|
## Changes in 0.6.2
|
|
16
23
|
|
|
17
24
|
- Namespace startup allows two minutes, with progress every five seconds, instead of failing after 12 seconds on a busy machine. `METALIST_STARTUP_TIMEOUT_SECONDS` can increase the allowance. Failed children are stopped and reaped.
|
|
@@ -86,7 +93,11 @@ uv tool install metalist
|
|
|
86
93
|
metalist
|
|
87
94
|
```
|
|
88
95
|
|
|
89
|
-
|
|
96
|
+
For a persistent uv installation, you can update from inside MetaList: type **update** in the menu, open **Version Info**, and choose **Update to …** when a newer release is available. Each browser load or refresh asks the server to check PyPI asynchronously. A reminder-style notice links to Version Info the first time a newer version is discovered; the announced version is remembered in that namespace's client preferences. A slow or failed check does not block the app. Version Info can retry a failed check.
|
|
97
|
+
|
|
98
|
+
The in-app updater checks the selected release, creates verified backups, and restarts every namespace. Keep Version Info open to follow progress and reload when ready; closing and reopening it in the same page resumes progress. Signing in again may be necessary. Source checkouts and unmanaged installations still check for releases, but show why in-app installation is unavailable. Update job records and logs live under `~/MetaList/update-jobs/` (or the configured data directory). If installation/restart fails after servers stop, inspect the updater output and run `metalist` on the server to restart it; there is no automatic rollback.
|
|
99
|
+
|
|
100
|
+
You can also update and restart MetaList with one cross-platform command:
|
|
90
101
|
```bash
|
|
91
102
|
metalist update
|
|
92
103
|
```
|
|
@@ -94,7 +105,7 @@ The updater checks the installed version against the latest PyPI release first.
|
|
|
94
105
|
|
|
95
106
|
Backups are saved to `~/MetaList/namespaces/<namespace>/backups/<namespace>-<timestamp>.metalist-backup.tar.gz`; the updater prints each verified path. Existing backups remain unchanged and are not pruned. If any backup fails, the update aborts with the current installation intact; run `metalist` to restart the stopped servers after resolving the failure.
|
|
96
107
|
|
|
97
|
-
Only after all backups pass does the updater hand off to an external PowerShell process on Windows or `/bin/sh` on macOS/Linux so the installed environment can unlock. It delegates installation to uv, pinning the current base interpreter and the exact tested package/dependency versions, using the cache populated during preflight with network access disabled. It then launches MetaList again, and reports the installed version (for example, `MetaList updated to v0.5.0.`). This protection requires an installed version containing the backup safeguard and applies to `metalist update
|
|
108
|
+
Only after all backups pass does the updater hand off to an external PowerShell process on Windows or `/bin/sh` on macOS/Linux so the installed environment can unlock. It delegates installation to uv, pinning the current base interpreter and the exact tested package/dependency versions, using the cache populated during preflight with network access disabled. It then launches MetaList again, and reports the installed version (for example, `MetaList updated to v0.5.0.`). This protection requires an installed version containing the backup safeguard and applies to `metalist update` and the in-app update action; direct pip/uv install commands do not run it.
|
|
98
109
|
|
|
99
110
|
For pip, users can run `pip install metalist`. For a non-editable local install from this checkout, use `uv pip install .` or `pip install .` instead of the editable command below.
|
|
100
111
|
|
|
@@ -19,6 +19,7 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|
|
19
19
|
f"{API_PREFIX}/auth/login-namespaces",
|
|
20
20
|
f"{API_PREFIX}/auth/login-namespaces/open",
|
|
21
21
|
f"{API_PREFIX}/auth/status",
|
|
22
|
+
f"{API_PREFIX}/auth/app-update/check",
|
|
22
23
|
f"{API_PREFIX}/auth/session",
|
|
23
24
|
"/favicon.ico",
|
|
24
25
|
"/locked",
|
|
@@ -29,6 +30,7 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
|
|
29
30
|
} | ({f"{API_PREFIX}/test/reset"} if TEST_MODE else set()))
|
|
30
31
|
|
|
31
32
|
PUBLIC_PREFIX_PATHS = (
|
|
33
|
+
f"{API_PREFIX}/auth/app-update/jobs/", # Opaque job capability survives restart/logout.
|
|
32
34
|
f"{API_PREFIX}/auth/namespaces/delete-jobs/",
|
|
33
35
|
f"{API_PREFIX}/auth/namespaces/rename-jobs/",
|
|
34
36
|
"/static/", # CSS/JS files needed for login page
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Authenticated in-app update initiation and opaque restart-safe job status."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, HTTPException
|
|
5
|
+
import httpx
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
from starlette.background import BackgroundTask
|
|
8
|
+
from starlette.responses import JSONResponse
|
|
9
|
+
|
|
10
|
+
from app.api.transactions import transactional_route
|
|
11
|
+
from app.services import app_updates
|
|
12
|
+
from app.services.exception_capture import CapturedExceptionContext
|
|
13
|
+
from app.services.app_updates import AppUpdateRejected
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
router = APIRouter(prefix='/auth/app-update', tags=['updates'])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AppUpdateRequest(BaseModel):
|
|
20
|
+
target_version: str = Field(pattern=r'^[0-9]+\.[0-9]+\.[0-9]+$')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@router.get('/check')
|
|
24
|
+
def check_update():
|
|
25
|
+
release = CapturedExceptionContext(httpx.HTTPError,
|
|
26
|
+
boundary='app/api/routes/app_updates.py:check_update:release')
|
|
27
|
+
with release:
|
|
28
|
+
status = app_updates.check_for_update()
|
|
29
|
+
if release.captured_exception is not None:
|
|
30
|
+
raise HTTPException(status_code=503, detail='Could not reach the release server. Please try again.') from release.captured_exception
|
|
31
|
+
return JSONResponse(content=status, headers={'Cache-Control': 'no-store'})
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@router.post('')
|
|
35
|
+
@transactional_route
|
|
36
|
+
def start_update(body: AppUpdateRequest):
|
|
37
|
+
admission = CapturedExceptionContext(AppUpdateRejected,
|
|
38
|
+
boundary='app/api/routes/app_updates.py:start_update:admission')
|
|
39
|
+
with admission:
|
|
40
|
+
record = app_updates.create_update_job(body.target_version)
|
|
41
|
+
if admission.captured_exception is not None:
|
|
42
|
+
raise HTTPException(status_code=409, detail=str(admission.captured_exception)) from admission.captured_exception
|
|
43
|
+
return JSONResponse(status_code=202, content=record,
|
|
44
|
+
background=BackgroundTask(app_updates.launch_update_job, record['job_id']))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@router.get('/jobs/{job_id}')
|
|
48
|
+
def update_status(job_id: str):
|
|
49
|
+
lookup = CapturedExceptionContext(AppUpdateRejected, FileNotFoundError,
|
|
50
|
+
boundary='app/api/routes/app_updates.py:update_status:lookup')
|
|
51
|
+
with lookup:
|
|
52
|
+
record = app_updates.read_job(job_id)
|
|
53
|
+
if lookup.captured_exception is not None:
|
|
54
|
+
raise HTTPException(status_code=404, detail='Update job not found') from lookup.captured_exception
|
|
55
|
+
return JSONResponse(content=record, headers={'Cache-Control': 'no-store'})
|
|
@@ -5,6 +5,13 @@ syntax suppressions and is consumed by runtime capture and the startup gate.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
CAPTURE_BOUNDARIES = {
|
|
8
|
+
# In-app updates translate only external transport, installation eligibility and job lookup errors.
|
|
9
|
+
'app/services/app_updates.py:check_for_update:eligibility': ('AppUpdateRejected',),
|
|
10
|
+
'app/services/app_updates.py:job_path:validation': ('ValueError',),
|
|
11
|
+
'app/services/app_updates.py:launch_update_job:launch': ('OSError',),
|
|
12
|
+
'app/api/routes/app_updates.py:check_update:release': ('HTTPError',),
|
|
13
|
+
'app/api/routes/app_updates.py:start_update:admission': ('AppUpdateRejected',),
|
|
14
|
+
'app/api/routes/app_updates.py:update_status:lookup': ('AppUpdateRejected', 'FileNotFoundError'),
|
|
8
15
|
# Release smoke polls only its own disposable HTTP listeners during an actual update.
|
|
9
16
|
'scripts/smoke_self_update.py:_has_updated_namespace:probe_capture': ('HTTPError',),
|
|
10
17
|
# A child can exit between poll and terminate; hung children must be killed and reaped.
|
|
@@ -15,6 +15,9 @@ HOP_HEADERS = frozenset({'connection', 'keep-alive', 'proxy-authenticate', 'prox
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class BoundedProxyServer(ThreadingHTTPServer):
|
|
18
|
+
# Admit a browser's initial connection burst before worker dispatch. The
|
|
19
|
+
# inherited five-entry accept queue can reset the sixth concurrent TLS open.
|
|
20
|
+
request_queue_size = MAX_CONNECTIONS
|
|
18
21
|
daemon_threads = True
|
|
19
22
|
|
|
20
23
|
def __init__(self, *args, **kwargs):
|
|
@@ -46,8 +49,8 @@ def make_proxy_handler(*, backend_host: str, backend_port: int, forward_headers)
|
|
|
46
49
|
protocol_version = 'HTTP/1.1'
|
|
47
50
|
|
|
48
51
|
def _proxy(self):
|
|
49
|
-
#
|
|
50
|
-
|
|
52
|
+
# Preserve the browser's HTTP/1.1 connection across module requests.
|
|
53
|
+
# Chunked response framing and the socket timeout bound each transfer.
|
|
51
54
|
lengths = self.headers.get_all('Content-Length', [])
|
|
52
55
|
transfers = self.headers.get_all('Transfer-Encoding', [])
|
|
53
56
|
if len(self.headers.get_all('Host', [])) != 1 or len(lengths) > 1 or (lengths and transfers):
|
|
@@ -103,7 +106,8 @@ def make_proxy_handler(*, backend_host: str, backend_port: int, forward_headers)
|
|
|
103
106
|
self.send_header('Transfer-Encoding', 'chunked')
|
|
104
107
|
elif response.getheader('Content-Length') is not None and self.command == 'HEAD':
|
|
105
108
|
self.send_header('Content-Length', response.getheader('Content-Length'))
|
|
106
|
-
self.
|
|
109
|
+
if self.close_connection:
|
|
110
|
+
self.send_header('Connection', 'close')
|
|
107
111
|
self.end_headers()
|
|
108
112
|
self.wfile.flush()
|
|
109
113
|
response_started = True
|
|
@@ -122,6 +126,8 @@ def make_proxy_handler(*, backend_host: str, backend_port: int, forward_headers)
|
|
|
122
126
|
self.wfile.flush()
|
|
123
127
|
# lint: allow-PY001 rationale="terminate a disconnected or failed external HTTP transport; emit only a sanitized upstream error"
|
|
124
128
|
except (OSError, http.client.HTTPException):
|
|
129
|
+
# A partial request/response cannot be reused for another request.
|
|
130
|
+
self.close_connection = True
|
|
125
131
|
if not response_started:
|
|
126
132
|
self.send_error(502, 'Upstream transport failed')
|
|
127
133
|
finally:
|
|
@@ -57,6 +57,7 @@ from app.services.diagnostics import track_request
|
|
|
57
57
|
from app.api.request_auth import clear_auth_cookie
|
|
58
58
|
from app.api.routes.notes import router as api2_router
|
|
59
59
|
from app.api.routes.auth import router as api2_auth_router
|
|
60
|
+
from app.api.routes.app_updates import router as api2_app_updates_router
|
|
60
61
|
from app.api.routes.files import router as api2_files_router
|
|
61
62
|
from app.api.routes.ontology import router as api2_ontology_router
|
|
62
63
|
from app.api.routes.backups import router as api2_backup_router
|
|
@@ -303,6 +304,7 @@ allowed_request_hosts = resolve_allowed_request_hosts(environ=os.environ)
|
|
|
303
304
|
# v2 routers mounted under configured API_PREFIX
|
|
304
305
|
app.include_router(api2_router, prefix=API_PREFIX, tags=["api2"])
|
|
305
306
|
app.include_router(api2_auth_router, prefix=API_PREFIX)
|
|
307
|
+
app.include_router(api2_app_updates_router, prefix=API_PREFIX)
|
|
306
308
|
app.include_router(api2_backup_router, prefix=API_PREFIX)
|
|
307
309
|
app.include_router(api2_files_router, prefix=API_PREFIX)
|
|
308
310
|
app.include_router(api2_ontology_router, prefix=API_PREFIX)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Detached launcher exits before the existing updater replaces its installation."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from app.services.app_updates import read_job, release_update_job, update_executable, write_job
|
|
8
|
+
from app.services.self_update import schedule_self_update
|
|
9
|
+
from app.version import __version__
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run_update(job_id: str) -> None:
|
|
13
|
+
record = read_job(job_id)
|
|
14
|
+
assert record['status'] == 'queued'
|
|
15
|
+
record = dict(record, status='preparing', pid=os.getpid(), message='Checking the update and preparing verified backups. MetaList will restart automatically.')
|
|
16
|
+
write_job(record)
|
|
17
|
+
# Finally records failures without intercepting internal exceptions.
|
|
18
|
+
scheduled = False
|
|
19
|
+
finished = False
|
|
20
|
+
try:
|
|
21
|
+
result = schedule_self_update(
|
|
22
|
+
current_version=__version__, metalist_executable=str(update_executable()),
|
|
23
|
+
current_pid=os.getpid(), platform_name=sys.platform, environ=os.environ,
|
|
24
|
+
expected_target_version=str(record['target_version']),
|
|
25
|
+
)
|
|
26
|
+
scheduled = result.update_scheduled
|
|
27
|
+
if scheduled:
|
|
28
|
+
status = 'installing'
|
|
29
|
+
else:
|
|
30
|
+
status = 'complete'
|
|
31
|
+
write_job(dict(record, status=status, message=result.message))
|
|
32
|
+
finished = True
|
|
33
|
+
finally:
|
|
34
|
+
if not finished:
|
|
35
|
+
write_job(dict(record, status='failed', message='Update failed. See the update log for the cause.'))
|
|
36
|
+
if not scheduled:
|
|
37
|
+
release_update_job(job_id)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == '__main__':
|
|
41
|
+
assert len(sys.argv) == 2, 'Usage: python -m app.services.app_update_worker JOB_ID'
|
|
42
|
+
run_update(sys.argv[1])
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Application-owned update jobs; only the existing installed updater installs code."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
import sysconfig
|
|
12
|
+
from uuid import UUID, uuid4
|
|
13
|
+
|
|
14
|
+
from app.data_directory import resolve_data_directory
|
|
15
|
+
from app.services.exception_capture import CapturedExceptionContext
|
|
16
|
+
from app.services.namespace_switcher import _is_process_running
|
|
17
|
+
from app.services.self_update import _fetch_latest_pypi_version, _release_version_key
|
|
18
|
+
from app.version import __version__
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if os.name == 'nt':
|
|
22
|
+
import msvcrt
|
|
23
|
+
else:
|
|
24
|
+
import fcntl
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AppUpdateRejected(ValueError):
|
|
28
|
+
"""An update cannot be started in this installation or job state."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def update_executable() -> Path:
|
|
32
|
+
receipt = Path(sys.prefix) / 'uv-receipt.toml'
|
|
33
|
+
if not receipt.is_file():
|
|
34
|
+
raise AppUpdateRejected('In-app updates require a uv-installed copy of MetaList. This running copy is not a managed installation.')
|
|
35
|
+
executable = Path(sysconfig.get_path('scripts')) / ('metalist.exe' if os.name == 'nt' else 'metalist')
|
|
36
|
+
if not executable.is_file():
|
|
37
|
+
raise AppUpdateRejected('The installed MetaList command is missing.')
|
|
38
|
+
if shutil.which('uv') is None:
|
|
39
|
+
raise AppUpdateRejected('The uv updater is not available on this server. Install uv before updating MetaList.')
|
|
40
|
+
return executable
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def check_for_update() -> dict[str, object]:
|
|
44
|
+
# Always contact PyPI, including in source checkouts; eligibility only controls installation.
|
|
45
|
+
target = _fetch_latest_pypi_version()
|
|
46
|
+
available = _release_version_key(target) > _release_version_key(__version__)
|
|
47
|
+
eligibility = CapturedExceptionContext(AppUpdateRejected,
|
|
48
|
+
boundary='app/services/app_updates.py:check_for_update:eligibility')
|
|
49
|
+
with eligibility:
|
|
50
|
+
update_executable()
|
|
51
|
+
supported = eligibility.captured_exception is None
|
|
52
|
+
if available:
|
|
53
|
+
message = f'Version {target} is available.'
|
|
54
|
+
else:
|
|
55
|
+
message = 'MetaList is up to date.'
|
|
56
|
+
if not supported:
|
|
57
|
+
message += f' {eligibility.captured_exception}'
|
|
58
|
+
return {'supported': supported, 'update_available': available, 'current_version': __version__,
|
|
59
|
+
'target_version': target, 'message': message}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def jobs_directory() -> Path:
|
|
63
|
+
return resolve_data_directory(environ=os.environ) / 'update-jobs'
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def job_path(job_id: str) -> Path:
|
|
67
|
+
validation = CapturedExceptionContext(ValueError,
|
|
68
|
+
boundary='app/services/app_updates.py:job_path:validation')
|
|
69
|
+
with validation:
|
|
70
|
+
normalized = str(UUID(job_id))
|
|
71
|
+
if validation.captured_exception is not None:
|
|
72
|
+
raise AppUpdateRejected('Invalid update job ID') from validation.captured_exception
|
|
73
|
+
if normalized != job_id:
|
|
74
|
+
raise AppUpdateRejected('Invalid update job ID')
|
|
75
|
+
return jobs_directory() / f'{job_id}.json'
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def write_job(record: dict[str, object]) -> None:
|
|
79
|
+
path = job_path(str(record['job_id']))
|
|
80
|
+
temporary = path.with_suffix(f'.{uuid4().hex}.tmp')
|
|
81
|
+
try:
|
|
82
|
+
with temporary.open('x', encoding='utf-8') as stream:
|
|
83
|
+
json.dump(record, stream)
|
|
84
|
+
os.replace(temporary, path)
|
|
85
|
+
finally:
|
|
86
|
+
temporary.unlink(missing_ok=True)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def read_job(job_id: str) -> dict[str, object]:
|
|
90
|
+
record = json.loads(job_path(job_id).read_text(encoding='utf-8'))
|
|
91
|
+
assert isinstance(record, dict) and record['job_id'] == job_id
|
|
92
|
+
assert record['status'] in {'queued', 'preparing', 'installing', 'complete', 'failed'}
|
|
93
|
+
assert isinstance(record['pid'], int) and record['pid'] > 0
|
|
94
|
+
_release_version_key(record['target_version'])
|
|
95
|
+
if record['status'] == 'installing' and _release_version_key(__version__) >= _release_version_key(record['target_version']):
|
|
96
|
+
record = dict(record, status='complete', message=f'MetaList {__version__} is ready. Reload to continue.')
|
|
97
|
+
elif record['status'] in {'queued', 'preparing'} and not _is_process_running(pid=record['pid']):
|
|
98
|
+
record = dict(record, status='failed', message='The update process stopped unexpectedly. See the update log.')
|
|
99
|
+
return record
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _create_update_job_locked(target_version: str) -> dict[str, object]:
|
|
103
|
+
update_executable()
|
|
104
|
+
_release_version_key(target_version)
|
|
105
|
+
if _release_version_key(target_version) <= _release_version_key(__version__):
|
|
106
|
+
raise AppUpdateRejected('The selected release is not newer than the running version.')
|
|
107
|
+
directory = jobs_directory()
|
|
108
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
active_path = directory / 'active.json'
|
|
110
|
+
if active_path.is_file():
|
|
111
|
+
active = json.loads(active_path.read_text(encoding='utf-8'))
|
|
112
|
+
if read_job(active['job_id'])['status'] not in {'complete', 'failed'}:
|
|
113
|
+
raise AppUpdateRejected('An update is already in progress. Wait for it to finish.')
|
|
114
|
+
active_path.unlink()
|
|
115
|
+
job_id = str(uuid4())
|
|
116
|
+
record = {'job_id': job_id, 'status': 'queued', 'current_version': __version__,
|
|
117
|
+
'target_version': target_version, 'pid': os.getpid(),
|
|
118
|
+
'message': 'Preparing the update. All namespaces will restart.',
|
|
119
|
+
'log_path': str(directory / f'{job_id}.log')}
|
|
120
|
+
write_job(record)
|
|
121
|
+
with active_path.open('x', encoding='utf-8') as stream:
|
|
122
|
+
json.dump({'job_id': job_id}, stream)
|
|
123
|
+
return record
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def launch_update_job(job_id: str) -> None:
|
|
127
|
+
record = read_job(job_id)
|
|
128
|
+
assert record['status'] == 'queued'
|
|
129
|
+
environment = dict(os.environ)
|
|
130
|
+
# Restart every saved namespace, preserving LAN/TLS/data settings.
|
|
131
|
+
for name in ('METALIST_NAMESPACE', 'METALIST_PORT', 'METALIST_HTTPS_PORT', 'METALIST_ORCHESTRATED_CHILD'):
|
|
132
|
+
if name in environment:
|
|
133
|
+
del environment[name]
|
|
134
|
+
options: dict[str, object] = {'env': environment, 'stdin': subprocess.DEVNULL}
|
|
135
|
+
if os.name == 'nt':
|
|
136
|
+
options['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
|
|
137
|
+
else:
|
|
138
|
+
options['start_new_session'] = True
|
|
139
|
+
launch = CapturedExceptionContext(OSError,
|
|
140
|
+
boundary='app/services/app_updates.py:launch_update_job:launch')
|
|
141
|
+
with launch:
|
|
142
|
+
with Path(str(record['log_path'])).open('xb') as log:
|
|
143
|
+
subprocess.Popen([sys.executable, '-m', 'app.services.app_update_worker', job_id],
|
|
144
|
+
stdout=log, stderr=subprocess.STDOUT, **options)
|
|
145
|
+
if launch.captured_exception is not None:
|
|
146
|
+
write_job(dict(record, status='failed', message='Could not start the updater. The running version was not changed.'))
|
|
147
|
+
release_update_job(job_id)
|
|
148
|
+
raise launch.captured_exception
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def release_update_job(job_id: str) -> None:
|
|
152
|
+
with _job_lock():
|
|
153
|
+
_release_update_job_locked(job_id)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _release_update_job_locked(job_id: str) -> None:
|
|
157
|
+
path = jobs_directory() / 'active.json'
|
|
158
|
+
if path.is_file():
|
|
159
|
+
active = json.loads(path.read_text(encoding='utf-8'))
|
|
160
|
+
if active['job_id'] == job_id:
|
|
161
|
+
path.unlink()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@contextmanager
|
|
165
|
+
def _job_lock():
|
|
166
|
+
directory = jobs_directory()
|
|
167
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
168
|
+
# Stable cross-process lock: all namespaces share this installation's update admission.
|
|
169
|
+
with (directory / 'admission.lock').open('a+b') as handle:
|
|
170
|
+
if os.name == 'nt':
|
|
171
|
+
if handle.tell() == 0:
|
|
172
|
+
handle.write(b'0')
|
|
173
|
+
handle.flush()
|
|
174
|
+
handle.seek(0)
|
|
175
|
+
msvcrt.locking(handle.fileno(), msvcrt.LK_LOCK, 1)
|
|
176
|
+
else:
|
|
177
|
+
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
|
|
178
|
+
try:
|
|
179
|
+
yield
|
|
180
|
+
finally:
|
|
181
|
+
if os.name == 'nt':
|
|
182
|
+
handle.seek(0)
|
|
183
|
+
msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
|
|
184
|
+
else:
|
|
185
|
+
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def create_update_job(target_version: str) -> dict[str, object]:
|
|
189
|
+
with _job_lock():
|
|
190
|
+
return _create_update_job_locked(target_version)
|
|
@@ -45,6 +45,7 @@ _ALLOWED_CLIENT_PREFERENCES = {
|
|
|
45
45
|
"pref.show_search_results_count": {"true", "false"},
|
|
46
46
|
"pref.show_ai_chat": {"true", "false"},
|
|
47
47
|
"pref.animated_transitions": {"true", "false"},
|
|
48
|
+
"pref.update_notice_version": "release_version",
|
|
48
49
|
"pref.reminder_surface_expanded": {"true", "false"},
|
|
49
50
|
"pref.note_layout.top_level_note_size": {"same", "larger", "largest"},
|
|
50
51
|
"pref.note_layout.child_indentation": {"compact", "standard", "wide"},
|
|
@@ -204,7 +205,10 @@ def _validate_client_preferences(preferences: dict[str, object]) -> dict[str, st
|
|
|
204
205
|
if not isinstance(value, str):
|
|
205
206
|
raise ClientStateValidationError(f"Client preference {key} must be a string")
|
|
206
207
|
allowed_values = _ALLOWED_CLIENT_PREFERENCES[key]
|
|
207
|
-
if allowed_values == "
|
|
208
|
+
if allowed_values == "release_version":
|
|
209
|
+
if re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", value) is None:
|
|
210
|
+
raise ClientStateValidationError("Invalid update notice version")
|
|
211
|
+
elif allowed_values == "tag_activity_windows":
|
|
208
212
|
_validate_tag_activity_windows_preference(key=key, value=value)
|
|
209
213
|
elif allowed_values == "ollama_base_url":
|
|
210
214
|
value = normalize_ollama_base_url(value)
|
|
@@ -43,6 +43,8 @@ def schedule_self_update(
|
|
|
43
43
|
current_pid: int,
|
|
44
44
|
platform_name: str,
|
|
45
45
|
environ: Mapping[str, str],
|
|
46
|
+
# lint: allow-PY002 rationale="Preserve the existing CLI scheduler contract; the in-app caller supplies the reviewed release pin"
|
|
47
|
+
expected_target_version: str | None = None,
|
|
46
48
|
) -> SelfUpdateScheduleResult:
|
|
47
49
|
_validate_schedule_inputs(
|
|
48
50
|
current_version=current_version,
|
|
@@ -51,6 +53,10 @@ def schedule_self_update(
|
|
|
51
53
|
platform_name=platform_name,
|
|
52
54
|
)
|
|
53
55
|
target_version = _fetch_latest_pypi_version()
|
|
56
|
+
if expected_target_version is not None:
|
|
57
|
+
_release_version_key(expected_target_version)
|
|
58
|
+
if target_version != expected_target_version:
|
|
59
|
+
raise RuntimeError('The available release changed; check for updates again. MetaList was not stopped.')
|
|
54
60
|
current_version_key = _release_version_key(current_version)
|
|
55
61
|
target_version_key = _release_version_key(target_version)
|
|
56
62
|
if current_version_key >= target_version_key:
|
|
@@ -37,7 +37,7 @@ class ShellCapacityError(ValueError):
|
|
|
37
37
|
def _terminate_tree(process) -> None:
|
|
38
38
|
with CapturedExceptionContext(ProcessLookupError, boundary='app/services/shell_session_service.py:_terminate_tree:capture'):
|
|
39
39
|
if os.name == 'nt':
|
|
40
|
-
stop_windows_process(
|
|
40
|
+
stop_windows_process(process=process)
|
|
41
41
|
else:
|
|
42
42
|
os.killpg(process.pid, signal.SIGKILL)
|
|
43
43
|
|
|
@@ -15,6 +15,7 @@ import httpx
|
|
|
15
15
|
from app.services.namespace_switcher import NamespaceLaunchProcess
|
|
16
16
|
from app.services.namespace_switcher import _stop_failed_namespace_launch
|
|
17
17
|
from app.services.namespace_switcher import _wait_for_namespace_ready
|
|
18
|
+
from app.services.windows_process_control import stop_process_tree as stop_windows_process_tree
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def _run_checked(command: list[str], *, environ: Mapping[str, str], directory: Path) -> str:
|
|
@@ -78,6 +79,10 @@ def _probe_candidate(*, python: str, executable: str, directory: Path,
|
|
|
78
79
|
if len(response.content) == 0:
|
|
79
80
|
raise RuntimeError(f"Update candidate has an empty runtime resource: {path}")
|
|
80
81
|
finally:
|
|
82
|
+
if sys.platform == "win32":
|
|
83
|
+
# The CLI/venv launchers can own a separate Python server process.
|
|
84
|
+
# Stop descendants even if readiness failure already reaped the launcher.
|
|
85
|
+
stop_windows_process_tree(process=process)
|
|
81
86
|
_stop_failed_namespace_launch(process=process)
|
|
82
87
|
|
|
83
88
|
|
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import shutil
|
|
4
4
|
import subprocess
|
|
5
5
|
import time
|
|
6
|
+
import ctypes
|
|
7
|
+
from ctypes import wintypes
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
_WAIT_POLL_INTERVAL_SECONDS = 0.25
|
|
@@ -145,20 +147,51 @@ def stop_process(*, pid: int) -> None:
|
|
|
145
147
|
raise RuntimeError(f"Timed out waiting for Windows process {pid} to exit")
|
|
146
148
|
|
|
147
149
|
|
|
148
|
-
def
|
|
149
|
-
"""
|
|
150
|
+
def _creation_filetime(process) -> int:
|
|
151
|
+
"""Read identity from the owned handle, which survives exit and prevents PID reuse."""
|
|
152
|
+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
153
|
+
get_times = kernel32.GetProcessTimes
|
|
154
|
+
get_times.argtypes = [wintypes.HANDLE, *([ctypes.POINTER(wintypes.FILETIME)] * 4)]
|
|
155
|
+
get_times.restype = wintypes.BOOL
|
|
156
|
+
created, exited, kernel, user = (wintypes.FILETIME() for _ in range(4))
|
|
157
|
+
if not get_times(int(process._handle), ctypes.byref(created), ctypes.byref(exited),
|
|
158
|
+
ctypes.byref(kernel), ctypes.byref(user)):
|
|
159
|
+
raise ctypes.WinError(ctypes.get_last_error())
|
|
160
|
+
return (created.dwHighDateTime << 32) | created.dwLowDateTime
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def stop_process_tree(*, process) -> None:
|
|
164
|
+
"""Stop and wait for descendants, including children of an exited parent."""
|
|
165
|
+
pid = process.pid
|
|
150
166
|
_validate_pid(pid=pid)
|
|
167
|
+
created_filetime = _creation_filetime(process)
|
|
151
168
|
_run_powershell(
|
|
152
169
|
script=(
|
|
170
|
+
"$ErrorActionPreference = 'Stop'; "
|
|
153
171
|
"$all = @(Get-CimInstance Win32_Process -ErrorAction Stop); "
|
|
154
172
|
f"$pending = [System.Collections.Generic.List[int]]::new(); $pending.Add({pid}); "
|
|
173
|
+
f"$created = @{{}}; $created[{pid}] = [datetime]::FromFileTimeUtc({created_filetime}); "
|
|
155
174
|
"$seen = [System.Collections.Generic.HashSet[int]]::new(); "
|
|
156
175
|
"for ($i=0; $i -lt $pending.Count; $i++) { "
|
|
157
176
|
"$parent = $pending[$i]; if (-not $seen.Add($parent)) { continue }; "
|
|
158
|
-
"foreach ($child in $all) { if ($child.ParentProcessId -eq $parent) {
|
|
177
|
+
"foreach ($child in $all) { if ($child.ParentProcessId -eq $parent) { "
|
|
178
|
+
"if ($null -eq $child.CreationDate) { throw 'Missing child process creation time' }; "
|
|
179
|
+
"$birth = $child.CreationDate.ToUniversalTime(); "
|
|
180
|
+
"if ($birth -ge $created[$parent]) { "
|
|
181
|
+
"$pending.Add([int]$child.ProcessId); $created[[int]$child.ProcessId] = $birth } } } }; "
|
|
159
182
|
"for ($i=$pending.Count-1; $i -ge 0; $i--) { "
|
|
160
183
|
"$target = Get-Process -Id $pending[$i] -ErrorAction SilentlyContinue; "
|
|
161
|
-
"if ($null -ne $target) {
|
|
184
|
+
"if ($null -ne $target) { try { "
|
|
185
|
+
"$null = $target.Handle; "
|
|
186
|
+
"if ($target.StartTime.ToUniversalTime().ToString('yyyyMMddHHmmssffffff') -ne "
|
|
187
|
+
"$created[$pending[$i]].ToString('yyyyMMddHHmmssffffff')) { throw 'Process identity changed before cleanup' }; "
|
|
188
|
+
"$target | Stop-Process -Force -ErrorAction Stop; "
|
|
189
|
+
f"if (-not $target.WaitForExit({int(_KILL_GRACE_SECONDS * 1000)})) {{ "
|
|
190
|
+
"throw ('Timed out waiting for process ' + $pending[$i] + ' to exit') } "
|
|
191
|
+
"} finally { $target.Dispose() } } }; "
|
|
192
|
+
# An absent launcher is expected after its child exits. Its final
|
|
193
|
+
# Get-Process lookup can leave $? false despite successful cleanup.
|
|
194
|
+
"exit 0"
|
|
162
195
|
),
|
|
163
196
|
operation=f"stopping process tree {pid}",
|
|
164
197
|
)
|
|
@@ -43,6 +43,9 @@ PY_ALLOWED_EXCEPTION_NAMES = (
|
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
JS_ALLOWED_TRY_CALLEE_NAMES = (
|
|
46
|
+
# HTTP update requests and write-through notification preferences; catches rethrow internal bugs.
|
|
47
|
+
"updateRequest",
|
|
48
|
+
"persistUpdateNotice",
|
|
46
49
|
"fetch",
|
|
47
50
|
"JSON.parse",
|
|
48
51
|
"clearAiChatSession",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
"id": "form.version_info",
|
|
121
|
-
"tags": ["version", "versions", "schema", "database", "sqlite", "migration", "migrations", "about", "info", "debug"]
|
|
121
|
+
"tags": ["update", "updates", "upgrade", "release", "pypi", "version", "versions", "schema", "database", "sqlite", "migration", "migrations", "about", "info", "debug"]
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
124
|
"id": "form.note_layout_appearance",
|
|
@@ -8365,3 +8365,19 @@ body.ai-tagging-locked .ai-chat-tag-operation select:enabled { cursor: pointer !
|
|
|
8365
8365
|
min-width: 0;
|
|
8366
8366
|
overflow-wrap: anywhere;
|
|
8367
8367
|
}
|
|
8368
|
+
|
|
8369
|
+
|
|
8370
|
+
/* Release notices use the reminder card styling without sharing reminder state. */
|
|
8371
|
+
.app-update-notice {
|
|
8372
|
+
position: fixed;
|
|
8373
|
+
right: 18px;
|
|
8374
|
+
bottom: 18px;
|
|
8375
|
+
z-index: 1100;
|
|
8376
|
+
max-width: min(460px, calc(100vw - 36px));
|
|
8377
|
+
display: flex;
|
|
8378
|
+
align-items: center;
|
|
8379
|
+
gap: 12px;
|
|
8380
|
+
}
|
|
8381
|
+
.app-update-notice button { flex-shrink: 0; }
|
|
8382
|
+
.version-update-section { margin-top: 18px; }
|
|
8383
|
+
.version-update-log { overflow-wrap: anywhere; font-size: 0.85em; }
|