MetaList 0.17.0__tar.gz → 0.18.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {metalist-0.17.0 → metalist-0.18.0}/PKG-INFO +6 -1
- {metalist-0.17.0 → metalist-0.18.0}/README.md +5 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/notes.py +19 -3
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-notes.md +2 -2
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-releases.md +5 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-search.md +2 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-tags.md +2 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/search_history.py +3 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/search_index.py +109 -0
- metalist-0.18.0/app/services/tag_suggestion_reasons.py +326 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/tag_suggestions.py +182 -52
- {metalist-0.17.0 → metalist-0.18.0}/app/services/tag_term_matching.py +51 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/css/main.css +17 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/context-menu/context-menu-registry.js +16 -7
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/context-menu/context-menu-service.js +43 -2
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/context-menu-events.js +8 -5
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +14 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +44 -7
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +32 -4
- {metalist-0.17.0 → metalist-0.18.0}/app/version.py +1 -1
- {metalist-0.17.0 → metalist-0.18.0}/metalist.egg-info/PKG-INFO +6 -1
- {metalist-0.17.0 → metalist-0.18.0}/metalist.egg-info/SOURCES.txt +1 -0
- {metalist-0.17.0 → metalist-0.18.0}/MANIFEST.in +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/__init__.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/__init__.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/deps.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/dev.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/middleware/auth.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/note_requests.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/request_auth.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/ai.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/app_updates.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/auth.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/backups.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/files.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/ontology.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/reminders.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/remote_images.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/routes/test.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/transactions.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/api/upload_limits.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/config.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/data_directory.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/__init__.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/backup_settings_sql.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/editable_files_migration.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/engine.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/file_schema.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/file_session.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/files_sql.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/link_titles_sql.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/live_recovery.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/migrations.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/notes_sql.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/ontology_rules_sql.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/reminders_sql.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/retire_sounds.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/schema.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/search_history_sql.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/session.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/settings_sql.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/tab_state_sql.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/db/version.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/encryption_audit.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/exception_boundaries.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/https_proxy.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/main.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/models/commands.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/models/database.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/models/entities.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/models/enums.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/models/linked_list.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/models/list_operations.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/models/list_traversal.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/models/note_crud.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/models/utils.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/network_settings.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/presentation/__init__.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/presentation/login_page.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/presentation/render/__init__.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/presentation/render/note_renderer.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/presentation/templates.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/security/authenticated_logging.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/security/encryption.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/security/http_error_diagnostics.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/security/http_headers.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/security/note_html.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/security/request_boundary.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/security/request_hosts.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/security/sensitive_cache.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/security/sensitive_logging.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/security/shell_execution.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/security/validation_errors.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/server_runtime.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/__init__.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/__init__.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/action_log.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/agent_loop.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/agent_tools.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/citation_tokens.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/cloud_privacy.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/context.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/evidence_serialization.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/execution_errors.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/failure_explanations.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/help_catalog.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/history.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/inference.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/investigation.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/judging.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/menu_actions.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/model_policy.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/note_aliases.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/openai_cost_tracking.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/openai_inference.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/prompt_settings.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/prompts/__init__.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/prompts/agent.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/prompts/final-response.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/prompts/system.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/release_notes.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/retrieval_settings.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/runtime.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/scope.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skill_settings.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/__init__.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-ai.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-data.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-dictation.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-formatting.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-menus.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-privacy.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-references.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/help-reminders.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/staged-summary.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/tag-proposals.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/skills/web-browsing.md +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/staged_summary.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/strict_structured_outputs.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/structured_inference.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/summary_batch_store.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/tagging.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/tagging_run.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/token_estimation.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/tool_calling.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/trace.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/web_actions.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/web_capabilities.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/web_evidence.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/web_fetch.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/agent/web_settings.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/ai_chat.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/ai_chat_rendering.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/ai_chat_stream.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/app_update_worker.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/app_updates.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/auth.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/auth_cache_state.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/auth_service.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/backlink_index.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/backlinks.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/backup_service.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/backup_settings_service.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/bulk_operation.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/chess_rendering.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/client_state_service.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/code_rendering.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/content_cache.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/content_formatting.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/diagnostics.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/dictation_cleanup.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/diff_rendering.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/embedded_references.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/encryption.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/exception_capture.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/file_edit_sessions.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/file_registry.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/file_storage.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/file_text_preview.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/footnote_layout.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/footnote_rendering.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/hierarchy.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/html_export.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/html_unformatting.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/hydration_state.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/image_size_formatting.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/inline_image_dimensions.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/inline_image_occurrences.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/input_errors.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/integrity.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/latex_rendering.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/link_title_retry.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/link_titles.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/login_rate_limit.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/maintenance_mode.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/markdown_rendering.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/mermaid_note_rendering.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/namespace_deletion_jobs.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/namespace_deletion_worker.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/namespace_rename_jobs.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/namespace_rename_worker.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/namespace_runtime_guard.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/namespace_switcher.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/note_fullscreen.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/note_image_tags.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/note_ordering.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/note_store.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/ontology_rules_store.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/openai_credentials.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/password_note_fields.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/public_http.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/reference_presentation.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/reminders.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/remote_image_proxy.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/request_recovery.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/resource_limits.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/root_sorting.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/runtime_generation.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/runtime_hardening.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/runtime_lock.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/search_history_storage.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/search_query.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/search_text.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/selected_text_tag.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/self_update.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/session_timeout_service.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/shell_session_service.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/snapshot.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/store.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/structured_note_renderers.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/sync.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/sync_state.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/tab_state.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/tag_case.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/tag_ontology.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/tag_rename.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/test_reset.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/tokens.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/undo_state.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/update_backups.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/update_installer.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/update_preflight.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/view_cache.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/view_diff.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/view_state.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/services/windows_process_control.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/startup_console.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/startup_environment.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/startup_js_sanity.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/startup_sanity.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/startup_sanity_config.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/config/agent-menu-actions.json +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/config/command_palette_tags.json +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/css/floating-notes.css +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/css/forms.css +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/img/favicon.ico +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/locked.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/main.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/activity-tracker.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/agent-menu-actions.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/agent-retrieval-settings.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/agent-web-settings.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/ai-agent-debug-view.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/ai-chat-api.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/ai-chat-message-placement.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/ai-chat-panel-controller.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/ai-chat-panel-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/ai-thinking-level-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/bulk-elapsed-clock.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/bulk-proposal-ui.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/bulk-scope-question.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/cloud-privacy-policy.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/confirmation-card.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/confirmation-question.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/proposal-menu.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/ai-chat/summary-batch-preview.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/api-client.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/api-error-diagnostics.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/api-failure-classification-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/app-update-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/application-state.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/async-result.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/auth.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/client-state-api.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/client-state-migration.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/activation-auth-policy.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/backup-command-availability.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/command-palette-controller.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/endpoint-registry.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/note-layout-preferences.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/preferences-store.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/tag-config-loader.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/command-palette/usage-store.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/config.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/connectivity-monitor.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/context-menu/context-menu-target-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/disconnect-editing-state-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/dom-utils-fixed.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/dom-utils.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/editor-commands.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/editor-selection.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/editor-toolbar.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/elapsed-time.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/error-handler.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/error-overlay.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-actions.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-autosave.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-editor-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-editor-state.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-loader.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-preview-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/excalidraw/excalidraw-scene.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/expected-errors.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/location-flags.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/login-namespace-picker.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/maintenance.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/ai-agent-settings-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/backup-restore-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/backup-result-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/backup-settings-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/base-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/confirmation-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/delete-namespace-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/delete-namespace-validation.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/dictation-settings-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/help-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/link-title-retry-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/namespace-loading-page.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/namespace-modals.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/namespace-ports-profile.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/namespace-rename-validation.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/note-layout-appearance-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/ontology-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/password-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/prioritize-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/random-password-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/reminder-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/search-suggestion-statistics-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/tag-suggestion-settings-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/version-info-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/modals/visual-cues-modal.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/actions/content-actions.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/actions/note-actions.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/actions/ui-actions.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/focus-events.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/keyboard-events.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/mouse-events.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/mode-context.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/mode-logger.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/add-style-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/backlinks-navigation-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/deselect-selection-state-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/dictation-note-paste.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/dictation-paste-padding.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/dictation-paste-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/differential-view-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/dropped-file-preview-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/file-drop-policy-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/floating-note-dom.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/floating-note-layout.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/floating-note-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/footnote-navigation-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/history-selection-policy-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/history-shortcut-policy-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/link-title-prefetch-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/mermaid-render-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-delete-shortcut-policy-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-drag-indicator-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-fullscreen-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/note-timestamp-hover-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/paste-format-detection-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/reference-source-navigation-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/remote-image-proxy-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/remove-formatting-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/root-note-creation-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-suggestion-statistics-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-suggestion-window-labels-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-suggestion-windows-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/selected-formatting-removal-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/selected-text-tag-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tab-drag-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tag-bar-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tag-input-selection-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tag-proposal-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/untagged-view-indicator-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/mode-manager/services/viewport-hold-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/note-html-sanitizer.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/password-generator.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/password-operation-response.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/password-policy.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/password-strength.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/reminder-store.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/reminder-surface-service.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/selection-markers.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/session-auth.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/tag-token.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/modules/uuid.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/namespace-deleted.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/namespace-renamed.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/dompurify-3.4.16.LICENSE.txt +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/dompurify-3.4.16.min.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/excalidraw.css +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Bold.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Medium.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Regular.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-SemiBold.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Cascadia/CascadiaCode-Regular.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-41b173a47b57366892116a575a43e2b6.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Virgil/Virgil-Regular.woff2 +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1.LICENSE.txt +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/excalidraw-0.18.1.min.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/markdown-it-14.3.2.LICENSE.txt +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/markdown-it-14.3.2.min.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/mermaid-11.16.1.LICENSE.txt +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/mermaid-11.16.1.min.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/zxcvbn-4.4.2.LICENSE.txt +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/js/vendor/zxcvbn-4.4.2.js +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/static/note-html-policy.json +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/templates/_logo_mark.html +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/templates/base.html +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/templates/index.html +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/templates/locked.html +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/templates/maintenance.html +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/templates/namespace_deleted.html +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/templates/namespace_renamed.html +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/templates/notes_list.html +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/upload_limits.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/__init__.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/add_selected_text_tag.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/alphabetize_root_notes.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/base.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/bulk_tag_proposals.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/collapse.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/copy_note.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/create_child.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/create_file_preview_child.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/create_note.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/create_sibling.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/delete_subtree.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/delete_tag.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/expand.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/file_edit_session.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/indent.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/lock.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/move.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/move_to_top.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/outdent.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/paste_child.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/paste_sibling.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/prefetch_link_titles.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/prioritize.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/redo.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/rename_tag.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/reset_updated_at.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/resize_image.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/run_shell.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/search_comment_autofill.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/search_context_tags.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/set_collapse_bulk.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/set_collapse_in_context.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/split_note.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/tag_proposals.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/toggle_reference_mode.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/toggle_todo_done.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/undo.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/unformat_content.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/update_content.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/usecases/view.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/utils/__init__.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/utils/encryption.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/app/utils/text_utils.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/convert-from-legacy.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/main.py +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/metalist.egg-info/dependency_links.txt +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/metalist.egg-info/entry_points.txt +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/metalist.egg-info/requires.txt +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/metalist.egg-info/top_level.txt +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/pyproject.toml +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/scripts/generate-lan-cert.sh +0 -0
- {metalist-0.17.0 → metalist-0.18.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metalist
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
4
4
|
Summary: A minimalist single-user note-taking app with SSR, fast in-memory tree operations, and efficient sync.
|
|
5
5
|
Project-URL: Homepage, https://github.com/evolvingstuff/metalist
|
|
6
6
|
Project-URL: Repository, https://github.com/evolvingstuff/metalist
|
|
@@ -51,6 +51,11 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
|
|
|
51
51
|
- Multi-tab search contexts with server-persisted scroll/search state (survives browser restarts)
|
|
52
52
|
- Manual namespace backups/restores to a user-selected backup folder with retention controls
|
|
53
53
|
|
|
54
|
+
## Changes in 0.18.0
|
|
55
|
+
|
|
56
|
+
- Right-click a tag suggestion, in the tag bar or the search bar, to see why it is suggested. The menu starts with **Why suggested**: the words of the note that name the tag (or a synonym), the tags it goes with and on how many notes, notes sharing this note's tags, whether it already applies through a parent or a rule, or that nothing in the note points to it; then how often the tag is used. Search suggestions also say how often you worked with a tag lately (when that put it first), what your search so far finds, or what leaving it out removes. The reasons are MetaList's own ranking, not the AI's, and appear instantly.
|
|
57
|
+
- Fixed: right-clicking a suggestion closed the list and opened the menu of the note underneath (or the browser's own menu). The list, your typed letters and the cursor now stay as they were, and `Escape` on an open right-click menu closes only the menu instead of also leaving edit mode.
|
|
58
|
+
|
|
54
59
|
## Changes in 0.17.0
|
|
55
60
|
|
|
56
61
|
- A whole-view summary that fails or is stopped keeps the batches it finished; saying "try that again" continues it, running only the remaining batches with the original question.
|
|
@@ -12,6 +12,11 @@ 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.18.0
|
|
16
|
+
|
|
17
|
+
- Right-click a tag suggestion, in the tag bar or the search bar, to see why it is suggested. The menu starts with **Why suggested**: the words of the note that name the tag (or a synonym), the tags it goes with and on how many notes, notes sharing this note's tags, whether it already applies through a parent or a rule, or that nothing in the note points to it; then how often the tag is used. Search suggestions also say how often you worked with a tag lately (when that put it first), what your search so far finds, or what leaving it out removes. The reasons are MetaList's own ranking, not the AI's, and appear instantly.
|
|
18
|
+
- Fixed: right-clicking a suggestion closed the list and opened the menu of the note underneath (or the browser's own menu). The list, your typed letters and the cursor now stay as they were, and `Escape` on an open right-click menu closes only the menu instead of also leaving edit mode.
|
|
19
|
+
|
|
15
20
|
## Changes in 0.17.0
|
|
16
21
|
|
|
17
22
|
- A whole-view summary that fails or is stopped keeps the batches it finished; saying "try that again" continues it, running only the remaining batches with the original question.
|
|
@@ -138,7 +138,8 @@ from app.services.note_fullscreen import build_note_fullscreen_markup
|
|
|
138
138
|
from app.services.link_titles import link_title_store
|
|
139
139
|
from app.services.search_index import search_index
|
|
140
140
|
from app.services.dictation_cleanup import clean_dictated_paste
|
|
141
|
-
from app.services.
|
|
141
|
+
from app.services.tag_suggestion_reasons import search_suggestion_reasons
|
|
142
|
+
from app.services.tag_suggestions import suggest_tags_with_reasons_for_note
|
|
142
143
|
from app.services.client_state_service import suggests_already_applying_tags
|
|
143
144
|
from app.services.undo_state import reset_undo_stack
|
|
144
145
|
from app.usecases.prioritize import list_prioritize_tag_suggestions
|
|
@@ -496,9 +497,20 @@ def search_suggestions(request: Request, payload: SearchSuggestionsRequest) -> D
|
|
|
496
497
|
for selection in personalized_selections
|
|
497
498
|
if selection.tag in suggestions
|
|
498
499
|
]
|
|
500
|
+
# Why each is suggested, shown when it is right-clicked (docs/ui/controls.md).
|
|
501
|
+
facts = search_index.describe_search_suggestions(query=query, terms=suggestions)
|
|
502
|
+
recent_activity = {
|
|
503
|
+
selection.tag.casefold(): (selection.window_days, selection.activity_count)
|
|
504
|
+
for selection in personalized_selections
|
|
505
|
+
if selection.tag in suggestions
|
|
506
|
+
}
|
|
499
507
|
return {
|
|
500
508
|
"suggestions": suggestions,
|
|
501
509
|
"personalizedSuggestions": personalized_suggestions,
|
|
510
|
+
"reasons": {
|
|
511
|
+
tag: list(search_suggestion_reasons(facts=facts, term=tag, recent_activity=recent_activity))
|
|
512
|
+
for tag in suggestions
|
|
513
|
+
},
|
|
502
514
|
}
|
|
503
515
|
|
|
504
516
|
|
|
@@ -626,7 +638,7 @@ def tag_suggestions(request: Request, payload: TagSuggestionsRequest) -> Dict[st
|
|
|
626
638
|
|
|
627
639
|
_require_note_present(note_id, context="notes.tag-suggestions")
|
|
628
640
|
|
|
629
|
-
suggestions =
|
|
641
|
+
suggestions = suggest_tags_with_reasons_for_note(
|
|
630
642
|
note_id=note_id,
|
|
631
643
|
anchors=anchors,
|
|
632
644
|
explicit_tags=explicit_tags,
|
|
@@ -635,7 +647,11 @@ def tag_suggestions(request: Request, payload: TagSuggestionsRequest) -> Dict[st
|
|
|
635
647
|
limit=MAX_TAG_SUGGESTIONS,
|
|
636
648
|
include_already_applying=suggests_already_applying_tags(token=_require_bearer_token(request)),
|
|
637
649
|
)
|
|
638
|
-
|
|
650
|
+
# Why each is suggested, shown when it is right-clicked (docs/ui/tag-bar.md).
|
|
651
|
+
return {
|
|
652
|
+
"suggestions": [suggestion.tag for suggestion in suggestions],
|
|
653
|
+
"reasons": {suggestion.tag: list(suggestion.reasons) for suggestion in suggestions},
|
|
654
|
+
}
|
|
639
655
|
|
|
640
656
|
|
|
641
657
|
@router.get("/notes/export-html")
|
|
@@ -172,7 +172,7 @@ On macOS, `⌘ + Y` normally opens Safari History rather than performing native
|
|
|
172
172
|
| Right-click image in note | Adds image actions to the note context menu: `Copy Image`, `Save Image`, `Zoom Image`, `Open Image in New Tab` |
|
|
173
173
|
| Right-click anywhere in a completed AI response bubble (including padding and links) | Choose `Copy Response` to place its raw Markdown on the MetaList note clipboard with `@markdown @llm`, while also writing rendered HTML and raw Markdown to the system clipboard. It can be pasted as a note but not as a reference until inserted. |
|
|
174
174
|
| Right-click notes view background | Open the view menu: outside edit mode `Add Note at Top`, `Paste Note at Top` (once a note is copied), `Undo`, `Redo`; `Go to Top`, `Go to Bottom` (unless at that end), `Fully Expand All`, `Fully Collapse All`; `View ▸` (All / Untagged notes), `Sort by ▸`, `Show ▸` (Chat, Tabs, Tags in List), `Theme ▸` (System, Light, Dark), each ticking the current choice; `New Tab`, `Open/Switch Namespace…`, `Reminders…`; `Export View as HTML`; `Log Out`. Shortcuts show on the right; items that do not apply are hidden. |
|
|
175
|
-
| Right-click tag in search input, tag bar, or their suggestion lists | Open tag context menu (Edit Tag Relationships)
|
|
175
|
+
| Right-click tag in search input, tag bar, or their suggestion lists | Open tag context menu (Edit Tag Relationships); a suggestion's menu (tag bar or search) starts with **Why suggested**. A suggestion list, the typed letters and edit mode stay as they were. Choosing Edit Tag Relationships saves an edited note first and completes the view refresh, restoring view-only rendering such as cached URL titles. |
|
|
176
176
|
| Right-click left or right lane | Open the same view menu |
|
|
177
177
|
| Hover left side of search bar | Show the tab/search-context overlay |
|
|
178
178
|
| Hover/click search input | Hide the tab/search-context overlay |
|
|
@@ -201,7 +201,7 @@ On macOS, `⌘ + Y` normally opens Safari History rather than performing native
|
|
|
201
201
|
|
|
202
202
|
> Note: On Windows/Linux, use `Ctrl` instead of `⌘` (Command)
|
|
203
203
|
|
|
204
|
-
App menus close with `Escape` or an outside click. The right-click menu also closes once the pointer moves about 28px away from it and any open submenu, like the tab overlay; small movements just past its edge keep it open. Every modal has a standard upper-right circular `×` matching full-screen note view and also closes with `Escape` or an outside click. Dismiss-only footer `Close`/`OK` buttons are omitted; `Enter` still submits a modal's declared primary workflow action.
|
|
204
|
+
App menus close with `Escape` (only the menu: a note being edited stays in edit mode) or an outside click. The right-click menu also closes once the pointer moves about 28px away from it and any open submenu, like the tab overlay; small movements just past its edge keep it open. Every modal has a standard upper-right circular `×` matching full-screen note view and also closes with `Escape` or an outside click. Dismiss-only footer `Close`/`OK` buttons are omitted; `Enter` still submits a modal's declared primary workflow action.
|
|
205
205
|
|
|
206
206
|
When `Show tags in list` is enabled, the grey right-aligned tag column wraps and is capped at 25% of its note row so note content retains the majority of the available width.
|
|
207
207
|
|
|
@@ -5,6 +5,11 @@ README. Changes made after the newest listed version are not described here.
|
|
|
5
5
|
Compare with installed_version from the help lookup to tell the user whether
|
|
6
6
|
they already have a release. Version info offers updates (see the data topic).
|
|
7
7
|
|
|
8
|
+
## 0.18.0
|
|
9
|
+
|
|
10
|
+
- Right-click a tag suggestion, in the tag bar or the search bar, to see why it is suggested. The menu starts with **Why suggested**: the words of the note that name the tag (or a synonym), the tags it goes with and on how many notes, notes sharing this note's tags, whether it already applies through a parent or a rule, or that nothing in the note points to it; then how often the tag is used. Search suggestions also say how often you worked with a tag lately (when that put it first), what your search so far finds, or what leaving it out removes. The reasons are MetaList's own ranking, not the AI's, and appear instantly.
|
|
11
|
+
- Fixed: right-clicking a suggestion closed the list and opened the menu of the note underneath (or the browser's own menu). The list, your typed letters and the cursor now stay as they were, and `Escape` on an open right-click menu closes only the menu instead of also leaving edit mode.
|
|
12
|
+
|
|
8
13
|
## 0.17.0
|
|
9
14
|
|
|
10
15
|
- A whole-view summary that fails or is stopped keeps the batches it finished; saying "try that again" continues it, running only the remaining batches with the original question.
|
|
@@ -144,6 +144,8 @@ Ontology rules also add **inferred tags** before search matching:
|
|
|
144
144
|
- A typed prefix can match either the start of the full tag or the start of any connector-separated segment.
|
|
145
145
|
- Example: `wor` suggests `workspaces` and `databricks-workspaces`.
|
|
146
146
|
- Example: `orksp` suggests neither.
|
|
147
|
+
- Right-clicking a search suggestion (Control-click on a Mac) opens its tag menu: **Why suggested**, then Edit Tag Relationships. The search and the list stay as they were; `Escape` closes only the menu. A right-click earns no activity credit.
|
|
148
|
+
- **Why suggested** says what MetaList's ranking used (not the AI): how often you worked with the tag in a personalization window (`You've worked with it 12 times in the last 7 days`), the notes it shares with the tags already in the search, how many notes the search so far finds it on, how many notes leaving it out removes, or that a first tag is listed by use; last, how many notes searching for it finds.
|
|
147
149
|
- The server returns at most `MAX_SEARCH_SUGGESTIONS` suggestions; the default is 20.
|
|
148
150
|
- This affects suggestions only; actual tag search matching remains exact against effective tag terms.
|
|
149
151
|
|
|
@@ -161,6 +161,8 @@ Unclosed comments behave like unclosed wrappers:
|
|
|
161
161
|
- Tags declared in ontology rules remain eligible for literal-content matching and typed-prefix completion even when they have never been explicitly assigned to a note. With a blank prefix, unrelated unused ontology tags are not added as generic fallback suggestions.
|
|
162
162
|
- Tags already present explicitly in the current tag bar are never suggested.
|
|
163
163
|
- Tags already present only via inheritance or ontology inference are suppressed unless they match the prefix. For one-character prefixes they come right after the suggestions with evidence; for longer prefixes they appear at the bottom. The **Tag Suggestions** settings dialog has `Allow suggesting tags that already apply` (off by default): when on, tags a parent already has and tags the rules imply from them are ranked like any other tag. Tags written in the note's own bar are still never suggested.
|
|
164
|
+
- Right-clicking a suggestion (Control-click on a Mac) opens the tag menu for it: **Why suggested**, then Edit Tag Relationships. The list, the typed letters and edit mode stay as they were; `Escape` closes only the menu.
|
|
165
|
+
- **Why suggested** says what the ranking found, then how often the tag is used: named (or partly named) in the note's text, a synonym named in it, the rest of the tag after what was typed, on N notes together with the note's tags, named in the note as last saved, on N notes that share the note's tags, or already applying through a parent or a rule. `Nothing in this note points to it` means it is listed only because it is used often. The reasons are MetaList's own ranking, not the AI's.
|
|
164
166
|
- Suggestions appear below the tag bar when there is room; if the tag bar is near the bottom, the list flips upward but keeps the same best-on-top ordering and initial scroll position.
|
|
165
167
|
- The server returns at most `MAX_TAG_SUGGESTIONS` note tag suggestions; the default is 20.
|
|
166
168
|
|
|
@@ -52,6 +52,8 @@ class TagActivityState:
|
|
|
52
52
|
class TagActivityWindowSelection:
|
|
53
53
|
tag: str
|
|
54
54
|
window_days: int
|
|
55
|
+
# The tag's activity credits in that window (why it won the slot).
|
|
56
|
+
activity_count: int
|
|
55
57
|
|
|
56
58
|
|
|
57
59
|
def current_local_date() -> date:
|
|
@@ -153,6 +155,7 @@ def rank_tag_activity_window_selections(
|
|
|
153
155
|
TagActivityWindowSelection(
|
|
154
156
|
tag=candidate_by_casefold[winner_casefold],
|
|
155
157
|
window_days=day_count,
|
|
158
|
+
activity_count=totals_by_casefold[winner_casefold],
|
|
156
159
|
)
|
|
157
160
|
)
|
|
158
161
|
return selections
|
|
@@ -128,6 +128,42 @@ def _parse_search_query_for_suggestions(raw_input: str) -> tuple[Tuple[str, ...]
|
|
|
128
128
|
return context.anchors, context.partial_prefix
|
|
129
129
|
|
|
130
130
|
|
|
131
|
+
@dataclass(frozen=True, slots=True)
|
|
132
|
+
class TagCooccurrence:
|
|
133
|
+
"""A tag's notes that carry the most of some anchor tags, as tag suggestions rank them."""
|
|
134
|
+
|
|
135
|
+
anchor_count: int
|
|
136
|
+
note_count: int
|
|
137
|
+
# The anchors (casefolded) those notes carry; one set when they all carry the same ones.
|
|
138
|
+
anchor_sets: FrozenSet[FrozenSet[str]]
|
|
139
|
+
|
|
140
|
+
def __post_init__(self) -> None:
|
|
141
|
+
if self.anchor_count <= 0 or self.note_count <= 0:
|
|
142
|
+
raise ValueError("A tag co-occurrence needs at least one anchor and one note")
|
|
143
|
+
if not self.anchor_sets or any(len(anchors) != self.anchor_count for anchors in self.anchor_sets):
|
|
144
|
+
raise ValueError("Every anchor set holds anchor_count anchors")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass(frozen=True, slots=True)
|
|
148
|
+
class SearchSuggestionFacts:
|
|
149
|
+
"""What the search-bar ranking used for some shown suggestions, to say why each is there."""
|
|
150
|
+
|
|
151
|
+
partial_prefix: str
|
|
152
|
+
# Tags (and words) already typed in the clause; with them the ranking is by co-occurrence.
|
|
153
|
+
anchors: Tuple[str, ...]
|
|
154
|
+
has_completed_clause: bool
|
|
155
|
+
is_exclusion: bool
|
|
156
|
+
# Notes the search so far finds (every note when nothing precedes the typed tag).
|
|
157
|
+
matching_note_count: int
|
|
158
|
+
note_counts: Dict[str, int]
|
|
159
|
+
matching_counts: Dict[str, int]
|
|
160
|
+
cooccurrences: Dict[str, TagCooccurrence]
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def is_ranked_by_cooccurrence(self) -> bool:
|
|
164
|
+
return any(anchor and not anchor.startswith("@") for anchor in self.anchors)
|
|
165
|
+
|
|
166
|
+
|
|
131
167
|
@dataclass(frozen=True, slots=True)
|
|
132
168
|
class SearchRecord:
|
|
133
169
|
note_id: str
|
|
@@ -513,6 +549,79 @@ class SearchIndex:
|
|
|
513
549
|
if term and not term.startswith("@")
|
|
514
550
|
}
|
|
515
551
|
|
|
552
|
+
def describe_tag_cooccurrence(self, *, anchors: Iterable[str], terms: Iterable[str]) -> Dict[str, TagCooccurrence]:
|
|
553
|
+
"""For each term, the notes carrying it together with the most of the anchors.
|
|
554
|
+
|
|
555
|
+
The same measure `suggest_tag_completions` ranks by (the most anchors one
|
|
556
|
+
of the term's notes carries, and how many notes carry that many), with
|
|
557
|
+
the anchors named. Terms on no note with an anchor are left out.
|
|
558
|
+
"""
|
|
559
|
+
anchor_casefold_set = frozenset(
|
|
560
|
+
anchor.casefold() for anchor in anchors if anchor and not anchor.startswith("@")
|
|
561
|
+
)
|
|
562
|
+
described: Dict[str, TagCooccurrence] = {}
|
|
563
|
+
with self._lock:
|
|
564
|
+
for term in terms:
|
|
565
|
+
term_casefold = term.casefold()
|
|
566
|
+
if term_casefold not in self._tag_notes_casefold:
|
|
567
|
+
continue
|
|
568
|
+
term_notes = self._tag_notes_casefold[term_casefold]
|
|
569
|
+
anchors_by_note: Dict[int, Set[str]] = {}
|
|
570
|
+
for anchor_casefold in anchor_casefold_set:
|
|
571
|
+
if anchor_casefold == term_casefold or anchor_casefold not in self._tag_notes_casefold:
|
|
572
|
+
continue
|
|
573
|
+
for note_id in term_notes & self._tag_notes_casefold[anchor_casefold]:
|
|
574
|
+
anchors_by_note.setdefault(note_id, set()).add(anchor_casefold)
|
|
575
|
+
if not anchors_by_note:
|
|
576
|
+
continue
|
|
577
|
+
most_anchors = max(len(note_anchors) for note_anchors in anchors_by_note.values())
|
|
578
|
+
at_most = [frozenset(note_anchors) for note_anchors in anchors_by_note.values()
|
|
579
|
+
if len(note_anchors) == most_anchors]
|
|
580
|
+
described[term] = TagCooccurrence(
|
|
581
|
+
anchor_count=most_anchors, note_count=len(at_most), anchor_sets=frozenset(at_most),
|
|
582
|
+
)
|
|
583
|
+
return described
|
|
584
|
+
|
|
585
|
+
def describe_search_suggestions(self, *, query: str, terms: Iterable[str]) -> SearchSuggestionFacts:
|
|
586
|
+
"""The facts `suggest_all_tag_completions` ranked and filtered these terms by."""
|
|
587
|
+
context = _parse_search_suggestion_context(query)
|
|
588
|
+
partial_prefix = ""
|
|
589
|
+
if context.partial_prefix is not None:
|
|
590
|
+
partial_prefix = context.partial_prefix
|
|
591
|
+
shown = list(terms)
|
|
592
|
+
with self._lock:
|
|
593
|
+
has_completed_clause = context.completed_clause.strip() != ""
|
|
594
|
+
if has_completed_clause:
|
|
595
|
+
clause = parse_search_query(context.completed_clause).first_clause
|
|
596
|
+
matching_ids = self._matching_note_int_ids_for_clause_locked(clause)
|
|
597
|
+
else:
|
|
598
|
+
matching_ids = self._alive
|
|
599
|
+
note_counts: Dict[str, int] = {}
|
|
600
|
+
matching_counts: Dict[str, int] = {}
|
|
601
|
+
for term in shown:
|
|
602
|
+
posting: FrozenSet[int] = frozenset()
|
|
603
|
+
if term.casefold() in self._tag_notes_casefold:
|
|
604
|
+
posting = frozenset(self._tag_notes_casefold[term.casefold()])
|
|
605
|
+
note_counts[term] = len(posting)
|
|
606
|
+
matching_counts[term] = len(posting & matching_ids)
|
|
607
|
+
cooccurrences = self.describe_tag_cooccurrence(anchors=context.anchors, terms=shown)
|
|
608
|
+
return SearchSuggestionFacts(
|
|
609
|
+
partial_prefix=partial_prefix, anchors=tuple(context.anchors),
|
|
610
|
+
has_completed_clause=has_completed_clause, is_exclusion=context.is_exclusion,
|
|
611
|
+
matching_note_count=len(matching_ids), note_counts=note_counts,
|
|
612
|
+
matching_counts=matching_counts, cooccurrences=cooccurrences,
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
def count_notes_with_tag(self, term: str) -> int:
|
|
616
|
+
"""Notes carrying the tag, as suggestions count usage (case-insensitive)."""
|
|
617
|
+
if not isinstance(term, str) or term == "":
|
|
618
|
+
raise TypeError("term must be a non-empty string")
|
|
619
|
+
with self._lock:
|
|
620
|
+
term_casefold = term.casefold()
|
|
621
|
+
if term_casefold not in self._tag_notes_casefold:
|
|
622
|
+
return 0
|
|
623
|
+
return len(self._tag_notes_casefold[term_casefold])
|
|
624
|
+
|
|
516
625
|
def suggest_tag_completions(self, *, query: str, limit: int) -> List[str]:
|
|
517
626
|
if not isinstance(query, str):
|
|
518
627
|
raise TypeError(f"query must be a string, got {type(query)}")
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""Why a tag is suggested, in the tag bar or the search bar: short reasons shown on right-click.
|
|
2
|
+
|
|
3
|
+
The facts come from the rankings (`tag_suggestions` for the tag bar: what the
|
|
4
|
+
note's text names, which of its tags the tag goes with, which notes share its
|
|
5
|
+
tags; the search index and search history for the search bar): the reasons say
|
|
6
|
+
what the ranking used, never a second guess at it. Only the shown suggestions
|
|
7
|
+
are explained (docs/ui/tag-bar.md, docs/ui/controls.md).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Dict, FrozenSet, Iterable, List, Mapping, Tuple
|
|
14
|
+
|
|
15
|
+
from app.services.search_index import SearchIndex
|
|
16
|
+
from app.services.search_index import SearchSuggestionFacts
|
|
17
|
+
from app.services.search_index import TagCooccurrence
|
|
18
|
+
from app.services.tag_ontology import TagOntology
|
|
19
|
+
from app.services.tag_term_matching import TagContentMatch
|
|
20
|
+
from app.services.tag_term_matching import quote_content_match
|
|
21
|
+
|
|
22
|
+
NOTHING_POINTS_TO_IT = "Nothing in this note points to it"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class TagSuggestion:
|
|
27
|
+
tag: str
|
|
28
|
+
reasons: Tuple[str, ...]
|
|
29
|
+
|
|
30
|
+
def __post_init__(self) -> None:
|
|
31
|
+
if not isinstance(self.tag, str) or self.tag == "":
|
|
32
|
+
raise ValueError("A tag suggestion needs a tag")
|
|
33
|
+
if not isinstance(self.reasons, tuple) or not self.reasons:
|
|
34
|
+
raise ValueError(f"Tag suggestion {self.tag!r} needs at least one reason")
|
|
35
|
+
for reason in self.reasons:
|
|
36
|
+
if not isinstance(reason, str) or reason.strip() == "":
|
|
37
|
+
raise ValueError(f"Tag suggestion {self.tag!r} has an empty reason")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class ContextOverlap:
|
|
42
|
+
"""Other notes that share the most of this note's tags and have the term written on them."""
|
|
43
|
+
|
|
44
|
+
shared_count: int
|
|
45
|
+
note_count: int
|
|
46
|
+
# The shared tags (casefolded) of those notes; a second set means they differ.
|
|
47
|
+
shared_casefold_sets: Tuple[FrozenSet[str], ...]
|
|
48
|
+
|
|
49
|
+
def __post_init__(self) -> None:
|
|
50
|
+
assert self.shared_count > 0 and self.note_count > 0
|
|
51
|
+
assert 1 <= len(self.shared_casefold_sets) <= 2
|
|
52
|
+
assert all(len(shared) == self.shared_count for shared in self.shared_casefold_sets)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _quoted(text: str) -> str:
|
|
56
|
+
return f'"{text}"'
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _join_names(names: Iterable[str]) -> str:
|
|
60
|
+
quoted = [_quoted(name) for name in sorted(names, key=lambda name: (name.casefold(), name))]
|
|
61
|
+
assert quoted
|
|
62
|
+
if len(quoted) == 1:
|
|
63
|
+
return quoted[0]
|
|
64
|
+
return ", ".join(quoted[:-1]) + " and " + quoted[-1]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _count_notes(count: int) -> str:
|
|
68
|
+
assert count > 0
|
|
69
|
+
if count == 1:
|
|
70
|
+
return "1 note"
|
|
71
|
+
return f"{count:,} notes"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _usage_line(count: int) -> str:
|
|
75
|
+
assert count > 0
|
|
76
|
+
return f"You've tagged {_count_notes(count)} with it"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _names_for(casefold_names: FrozenSet[str], display_by_casefold: Mapping[str, str]) -> List[str]:
|
|
80
|
+
for name in casefold_names:
|
|
81
|
+
assert name in display_by_casefold, name
|
|
82
|
+
return [display_by_casefold[name] for name in casefold_names]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _cooccurrence_phrase(cooccurrence: TagCooccurrence, anchors: Iterable[str], anchors_name: str) -> str:
|
|
86
|
+
"""`on 4 notes together with "python"`, naming the anchors those notes carry
|
|
87
|
+
(or how many of them, `anchors_name` saying whose they are)."""
|
|
88
|
+
notes = _count_notes(cooccurrence.note_count)
|
|
89
|
+
if len(cooccurrence.anchor_sets) > 1:
|
|
90
|
+
return f"on {notes} together with {cooccurrence.anchor_count} of {anchors_name}"
|
|
91
|
+
display_by_casefold = {anchor.casefold(): anchor for anchor in anchors}
|
|
92
|
+
(anchor_set,) = cooccurrence.anchor_sets
|
|
93
|
+
return f"on {notes} together with {_join_names(_names_for(anchor_set, display_by_casefold))}"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _count_times(count: int) -> str:
|
|
97
|
+
assert count > 0
|
|
98
|
+
if count == 1:
|
|
99
|
+
return "once"
|
|
100
|
+
if count == 2:
|
|
101
|
+
return "twice"
|
|
102
|
+
return f"{count:,} times"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(frozen=True)
|
|
106
|
+
class ReasonWriter:
|
|
107
|
+
"""Reasons any suggestion can have: whether it already applies, and its use."""
|
|
108
|
+
|
|
109
|
+
count_by_casefold: Mapping[str, int]
|
|
110
|
+
ontology_only_casefold: FrozenSet[str]
|
|
111
|
+
inherited_casefold: FrozenSet[str]
|
|
112
|
+
implied_casefold: FrozenSet[str]
|
|
113
|
+
base_tags: FrozenSet[str]
|
|
114
|
+
ontology: TagOntology
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def build(
|
|
118
|
+
cls, *, exact_tag_counts: Mapping[str, int], ontology_only_casefold: FrozenSet[str],
|
|
119
|
+
inherited_non_meta: FrozenSet[str], base_tags: FrozenSet[str], ontology: TagOntology,
|
|
120
|
+
) -> ReasonWriter:
|
|
121
|
+
count_by_casefold: Dict[str, int] = {}
|
|
122
|
+
for term, count in exact_tag_counts.items():
|
|
123
|
+
assert term.casefold() not in count_by_casefold, f"case variants are merged before counting: {term}"
|
|
124
|
+
count_by_casefold[term.casefold()] = count
|
|
125
|
+
base_casefold = frozenset(tag.casefold() for tag in base_tags)
|
|
126
|
+
implied_casefold: FrozenSet[str] = frozenset()
|
|
127
|
+
if not ontology.is_empty:
|
|
128
|
+
implied_casefold = frozenset(
|
|
129
|
+
tag.casefold() for tag in ontology.infer_implication_only(base_tags=base_tags)
|
|
130
|
+
) - base_casefold
|
|
131
|
+
return cls(
|
|
132
|
+
count_by_casefold=count_by_casefold, ontology_only_casefold=ontology_only_casefold,
|
|
133
|
+
inherited_casefold=frozenset(tag.casefold() for tag in inherited_non_meta),
|
|
134
|
+
implied_casefold=implied_casefold, base_tags=base_tags, ontology=ontology,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def already_applies(self, term: str) -> Tuple[str, ...]:
|
|
138
|
+
term_casefold = term.casefold()
|
|
139
|
+
if term_casefold in self.inherited_casefold:
|
|
140
|
+
return ("Already applies: inherited from a parent note",)
|
|
141
|
+
if term_casefold not in self.implied_casefold:
|
|
142
|
+
return ()
|
|
143
|
+
for base in sorted(self.base_tags, key=lambda tag: (tag.casefold(), tag)):
|
|
144
|
+
implied = self.ontology.infer_implication_only(base_tags=frozenset({base}))
|
|
145
|
+
if term_casefold in {tag.casefold() for tag in implied}:
|
|
146
|
+
return (f"Already applies: implied by {_quoted(base)}",)
|
|
147
|
+
# Implied only by several of the note's tags together.
|
|
148
|
+
return ("Already applies: implied by this note's tags",)
|
|
149
|
+
|
|
150
|
+
def usage(self, term: str) -> str:
|
|
151
|
+
term_casefold = term.casefold()
|
|
152
|
+
if term_casefold in self.count_by_casefold and self.count_by_casefold[term_casefold] > 0:
|
|
153
|
+
return _usage_line(self.count_by_casefold[term_casefold])
|
|
154
|
+
assert term_casefold in self.ontology_only_casefold, f"suggested tag {term!r} is on no note"
|
|
155
|
+
return "Not on any note yet; it is in your tag relationships"
|
|
156
|
+
|
|
157
|
+
def without_evidence(self, term: str) -> Tuple[str, ...]:
|
|
158
|
+
"""A tag listed for its use (or because it already applies), not for this note."""
|
|
159
|
+
already = self.already_applies(term)
|
|
160
|
+
if already:
|
|
161
|
+
return (*already, self.usage(term))
|
|
162
|
+
return (NOTHING_POINTS_TO_IT, self.usage(term))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def meta_tag_reasons(*, index: SearchIndex, term: str, prefix: str) -> Tuple[str, ...]:
|
|
166
|
+
"""Meta tags are listed by use once `@` is typed."""
|
|
167
|
+
assert term.startswith("@") and prefix.startswith("@"), (term, prefix)
|
|
168
|
+
count = index.count_notes_with_tag(term)
|
|
169
|
+
usage = "Not on any note yet"
|
|
170
|
+
if count > 0:
|
|
171
|
+
usage = _usage_line(count)
|
|
172
|
+
return (f"A built-in tag starting with {_quoted(prefix)}", usage)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@dataclass(frozen=True)
|
|
176
|
+
class SuggestionEvidence:
|
|
177
|
+
"""What one ranking found for its terms, to say why a shown tag is suggested."""
|
|
178
|
+
|
|
179
|
+
# The index the ranking used, for the co-occurring notes behind a reason.
|
|
180
|
+
index: SearchIndex
|
|
181
|
+
writer: ReasonWriter
|
|
182
|
+
prefix: str
|
|
183
|
+
content_matches: Mapping[str, TagContentMatch]
|
|
184
|
+
# Terms whose content match is the rest of the tag after the typed prefix.
|
|
185
|
+
prefix_remainder_terms: FrozenSet[str]
|
|
186
|
+
# Synonym named exactly in the content -> (the tag it is a synonym of, its match).
|
|
187
|
+
synonym_matches: Mapping[str, Tuple[str, TagContentMatch]]
|
|
188
|
+
cooccurrence_terms: FrozenSet[str]
|
|
189
|
+
cooccurrence_anchors: Tuple[str, ...]
|
|
190
|
+
saved_text_matches: Mapping[str, TagContentMatch]
|
|
191
|
+
overlaps: Mapping[str, ContextOverlap]
|
|
192
|
+
context_tags: FrozenSet[str]
|
|
193
|
+
# Shown tag -> the ranked terms it stands for (equivalent tags are shown once).
|
|
194
|
+
sources: Mapping[str, Tuple[str, ...]]
|
|
195
|
+
|
|
196
|
+
def reasons_for(self, tag: str) -> Tuple[str, ...]:
|
|
197
|
+
assert tag in self.sources, f"{tag!r} was not ranked"
|
|
198
|
+
subjects = [tag] + [term for term in self.sources[tag] if term != tag]
|
|
199
|
+
cooccurrences = self.index.describe_tag_cooccurrence(
|
|
200
|
+
anchors=self.cooccurrence_anchors,
|
|
201
|
+
terms=[subject for subject in subjects if subject in self.cooccurrence_terms],
|
|
202
|
+
)
|
|
203
|
+
lines: List[str] = []
|
|
204
|
+
for subject in subjects:
|
|
205
|
+
lines.extend(self._evidence(subject=subject, shown=tag, cooccurrences=cooccurrences))
|
|
206
|
+
lines.extend(self.writer.already_applies(tag))
|
|
207
|
+
if not lines:
|
|
208
|
+
lines.append(NOTHING_POINTS_TO_IT)
|
|
209
|
+
lines.append(self.writer.usage(tag))
|
|
210
|
+
return tuple(dict.fromkeys(lines))
|
|
211
|
+
|
|
212
|
+
def _evidence(self, *, subject: str, shown: str, cooccurrences: Mapping[str, TagCooccurrence]) -> List[str]:
|
|
213
|
+
lines: List[str] = []
|
|
214
|
+
if subject in self.synonym_matches:
|
|
215
|
+
representative, match = self.synonym_matches[subject]
|
|
216
|
+
lines.append(self._named(match=match, shown=shown, where="this note", is_remainder=False))
|
|
217
|
+
lines.append(f"A synonym of {_quoted(representative)}")
|
|
218
|
+
elif subject in self.content_matches:
|
|
219
|
+
lines.append(self._named(
|
|
220
|
+
match=self.content_matches[subject], shown=shown, where="this note",
|
|
221
|
+
is_remainder=subject in self.prefix_remainder_terms,
|
|
222
|
+
))
|
|
223
|
+
if subject in self.cooccurrence_terms:
|
|
224
|
+
assert subject in cooccurrences, f"{subject!r} was ranked as co-occurring"
|
|
225
|
+
lines.append(self._about(subject, shown, self._cooccurrence(cooccurrences[subject])))
|
|
226
|
+
if subject in self.saved_text_matches and subject not in self.content_matches:
|
|
227
|
+
lines.append(self._named(
|
|
228
|
+
match=self.saved_text_matches[subject], shown=shown, where="this note as last saved",
|
|
229
|
+
is_remainder=False,
|
|
230
|
+
))
|
|
231
|
+
if subject in self.overlaps and subject not in self.cooccurrence_terms:
|
|
232
|
+
overlap = self._overlap(subject=subject, overlap=self.overlaps[subject])
|
|
233
|
+
if overlap != "":
|
|
234
|
+
lines.append(self._about(subject, shown, overlap))
|
|
235
|
+
return lines
|
|
236
|
+
|
|
237
|
+
def _named(self, *, match: TagContentMatch, shown: str, where: str, is_remainder: bool) -> str:
|
|
238
|
+
quoted = ", ".join(_quoted(words) for words in quote_content_match(match))
|
|
239
|
+
# Whole only when every part of the tag was found (`topic-41` is partly named by "topic").
|
|
240
|
+
is_whole = len(match.evidence_raw_indexes) == match.raw_segment_count
|
|
241
|
+
if is_remainder:
|
|
242
|
+
return f"The rest of it after {_quoted(self.prefix)} is in {where}: {quoted}"
|
|
243
|
+
if match.term.casefold() == shown.casefold():
|
|
244
|
+
if is_whole:
|
|
245
|
+
return f"Named in {where}: {quoted}"
|
|
246
|
+
return f"Partly named in {where}: {quoted}"
|
|
247
|
+
where_capitalized = where[0].upper() + where[1:]
|
|
248
|
+
if is_whole:
|
|
249
|
+
return f"{where_capitalized} names its synonym {_quoted(match.term)}"
|
|
250
|
+
return f"{where_capitalized} partly names its synonym {_quoted(match.term)}: {quoted}"
|
|
251
|
+
|
|
252
|
+
def _cooccurrence(self, cooccurrence: TagCooccurrence) -> str:
|
|
253
|
+
return _cooccurrence_phrase(cooccurrence, self.cooccurrence_anchors, "this note's tags")
|
|
254
|
+
|
|
255
|
+
def _overlap(self, *, subject: str, overlap: ContextOverlap) -> str:
|
|
256
|
+
"""Empty when the only tag shared is the term itself (a tag that already
|
|
257
|
+
applies, ranked when that setting is on): notes with it have it."""
|
|
258
|
+
notes = _count_notes(overlap.note_count)
|
|
259
|
+
# The term itself is no evidence for itself.
|
|
260
|
+
shared_sets = [shared - {subject.casefold()} for shared in overlap.shared_casefold_sets]
|
|
261
|
+
if any(not shared for shared in shared_sets):
|
|
262
|
+
return ""
|
|
263
|
+
if len(shared_sets) > 1:
|
|
264
|
+
shared_count = min(len(shared) for shared in shared_sets)
|
|
265
|
+
return f"on {notes} sharing {shared_count} of this note's tags"
|
|
266
|
+
display_by_casefold = {tag.casefold(): tag for tag in self.context_tags}
|
|
267
|
+
names = _join_names(_names_for(shared_sets[0], display_by_casefold))
|
|
268
|
+
verb = "share"
|
|
269
|
+
if overlap.note_count == 1:
|
|
270
|
+
verb = "shares"
|
|
271
|
+
return f"on {notes} that {verb} {names} with this one"
|
|
272
|
+
|
|
273
|
+
@staticmethod
|
|
274
|
+
def _about(subject: str, shown: str, finding: str) -> str:
|
|
275
|
+
"""A finding about the shown tag, or about a synonym it is shown in place of."""
|
|
276
|
+
if subject == shown:
|
|
277
|
+
return finding[0].upper() + finding[1:]
|
|
278
|
+
return f"Its synonym {_quoted(subject)} is {finding}"
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def search_suggestion_reasons(
|
|
282
|
+
*, facts: SearchSuggestionFacts, term: str, recent_activity: Mapping[str, Tuple[int, int]],
|
|
283
|
+
) -> Tuple[str, ...]:
|
|
284
|
+
"""Why the search bar suggests a tag.
|
|
285
|
+
|
|
286
|
+
recent_activity: casefolded tag -> (window days, activity credits) for the
|
|
287
|
+
tags a personalization window put first.
|
|
288
|
+
"""
|
|
289
|
+
assert term in facts.note_counts, f"{term!r} was not described"
|
|
290
|
+
lines: List[str] = []
|
|
291
|
+
if term.casefold() in recent_activity:
|
|
292
|
+
window_days, activity_count = recent_activity[term.casefold()]
|
|
293
|
+
when = "today"
|
|
294
|
+
if window_days > 1:
|
|
295
|
+
when = f"in the last {window_days} days"
|
|
296
|
+
lines.append(f"You've worked with it {_count_times(activity_count)} {when}")
|
|
297
|
+
note_count = facts.note_counts[term]
|
|
298
|
+
if facts.partial_prefix.startswith("@"):
|
|
299
|
+
lines.append(f"A built-in tag starting with {_quoted(facts.partial_prefix)}")
|
|
300
|
+
if note_count == 0:
|
|
301
|
+
lines.append("Not on any note yet")
|
|
302
|
+
return tuple(lines)
|
|
303
|
+
lines.append(f"Searching for it finds {_count_notes(note_count)}")
|
|
304
|
+
return tuple(lines)
|
|
305
|
+
if facts.is_exclusion:
|
|
306
|
+
lines.append(_exclusion_line(facts=facts, term=term))
|
|
307
|
+
elif term in facts.cooccurrences:
|
|
308
|
+
finding = _cooccurrence_phrase(facts.cooccurrences[term], facts.anchors, "the tags in your search")
|
|
309
|
+
lines.append(finding[0].upper() + finding[1:])
|
|
310
|
+
elif facts.has_completed_clause:
|
|
311
|
+
lines.append(f"Your search so far finds it on {_count_notes(facts.matching_counts[term])}")
|
|
312
|
+
else:
|
|
313
|
+
lines.append("Tags are listed by how many notes have them")
|
|
314
|
+
assert note_count > 0, f"suggested search tag {term!r} is on no note"
|
|
315
|
+
lines.append(f"Searching for it finds {_count_notes(note_count)}")
|
|
316
|
+
return tuple(lines)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _exclusion_line(*, facts: SearchSuggestionFacts, term: str) -> str:
|
|
320
|
+
removed = facts.matching_counts[term]
|
|
321
|
+
of_notes = f"of your {facts.matching_note_count:,} notes"
|
|
322
|
+
if facts.has_completed_clause:
|
|
323
|
+
of_notes = f"of the {facts.matching_note_count:,} notes your search finds"
|
|
324
|
+
if removed == 0:
|
|
325
|
+
return f"Leaving it out removes none {of_notes}"
|
|
326
|
+
return f"Leaving it out removes {removed:,} {of_notes}"
|