#
tokens: 37668/50000 1/625 files (page 33/35)
lines: off (toggle) GitHub
raw markdown copy
This is page 33 of 35. Use http://codebase.md/doobidoo/mcp-memory-service?lines=false&page={x} to view the full context.

# Directory Structure

```
├── .claude
│   ├── agents
│   │   ├── amp-bridge.md
│   │   ├── amp-pr-automator.md
│   │   ├── code-quality-guard.md
│   │   ├── gemini-pr-automator.md
│   │   └── github-release-manager.md
│   ├── settings.local.json.backup
│   └── settings.local.json.local
├── .commit-message
├── .dockerignore
├── .env.example
├── .env.sqlite.backup
├── .envnn#
├── .gitattributes
├── .github
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── feature_request.yml
│   │   └── performance_issue.yml
│   ├── pull_request_template.md
│   └── workflows
│       ├── bridge-tests.yml
│       ├── CACHE_FIX.md
│       ├── claude-code-review.yml
│       ├── claude.yml
│       ├── cleanup-images.yml.disabled
│       ├── dev-setup-validation.yml
│       ├── docker-publish.yml
│       ├── LATEST_FIXES.md
│       ├── main-optimized.yml.disabled
│       ├── main.yml
│       ├── publish-and-test.yml
│       ├── README_OPTIMIZATION.md
│       ├── release-tag.yml.disabled
│       ├── release.yml
│       ├── roadmap-review-reminder.yml
│       ├── SECRET_CONDITIONAL_FIX.md
│       └── WORKFLOW_FIXES.md
├── .gitignore
├── .mcp.json.backup
├── .mcp.json.template
├── .pyscn
│   ├── .gitignore
│   └── reports
│       └── analyze_20251123_214224.html
├── AGENTS.md
├── archive
│   ├── deployment
│   │   ├── deploy_fastmcp_fixed.sh
│   │   ├── deploy_http_with_mcp.sh
│   │   └── deploy_mcp_v4.sh
│   ├── deployment-configs
│   │   ├── empty_config.yml
│   │   └── smithery.yaml
│   ├── development
│   │   └── test_fastmcp.py
│   ├── docs-removed-2025-08-23
│   │   ├── authentication.md
│   │   ├── claude_integration.md
│   │   ├── claude-code-compatibility.md
│   │   ├── claude-code-integration.md
│   │   ├── claude-code-quickstart.md
│   │   ├── claude-desktop-setup.md
│   │   ├── complete-setup-guide.md
│   │   ├── database-synchronization.md
│   │   ├── development
│   │   │   ├── autonomous-memory-consolidation.md
│   │   │   ├── CLEANUP_PLAN.md
│   │   │   ├── CLEANUP_README.md
│   │   │   ├── CLEANUP_SUMMARY.md
│   │   │   ├── dream-inspired-memory-consolidation.md
│   │   │   ├── hybrid-slm-memory-consolidation.md
│   │   │   ├── mcp-milestone.md
│   │   │   ├── multi-client-architecture.md
│   │   │   ├── test-results.md
│   │   │   └── TIMESTAMP_FIX_SUMMARY.md
│   │   ├── distributed-sync.md
│   │   ├── invocation_guide.md
│   │   ├── macos-intel.md
│   │   ├── master-guide.md
│   │   ├── mcp-client-configuration.md
│   │   ├── multi-client-server.md
│   │   ├── service-installation.md
│   │   ├── sessions
│   │   │   └── MCP_ENHANCEMENT_SESSION_MEMORY_v4.1.0.md
│   │   ├── UBUNTU_SETUP.md
│   │   ├── ubuntu.md
│   │   ├── windows-setup.md
│   │   └── windows.md
│   ├── docs-root-cleanup-2025-08-23
│   │   ├── AWESOME_LIST_SUBMISSION.md
│   │   ├── CLOUDFLARE_IMPLEMENTATION.md
│   │   ├── DOCUMENTATION_ANALYSIS.md
│   │   ├── DOCUMENTATION_CLEANUP_PLAN.md
│   │   ├── DOCUMENTATION_CONSOLIDATION_COMPLETE.md
│   │   ├── LITESTREAM_SETUP_GUIDE.md
│   │   ├── lm_studio_system_prompt.md
│   │   ├── PYTORCH_DOWNLOAD_FIX.md
│   │   └── README-ORIGINAL-BACKUP.md
│   ├── investigations
│   │   └── MACOS_HOOKS_INVESTIGATION.md
│   ├── litestream-configs-v6.3.0
│   │   ├── install_service.sh
│   │   ├── litestream_master_config_fixed.yml
│   │   ├── litestream_master_config.yml
│   │   ├── litestream_replica_config_fixed.yml
│   │   ├── litestream_replica_config.yml
│   │   ├── litestream_replica_simple.yml
│   │   ├── litestream-http.service
│   │   ├── litestream.service
│   │   └── requirements-cloudflare.txt
│   ├── release-notes
│   │   └── release-notes-v7.1.4.md
│   └── setup-development
│       ├── README.md
│       ├── setup_consolidation_mdns.sh
│       ├── STARTUP_SETUP_GUIDE.md
│       └── test_service.sh
├── CHANGELOG-HISTORIC.md
├── CHANGELOG.md
├── claude_commands
│   ├── memory-context.md
│   ├── memory-health.md
│   ├── memory-ingest-dir.md
│   ├── memory-ingest.md
│   ├── memory-recall.md
│   ├── memory-search.md
│   ├── memory-store.md
│   ├── README.md
│   └── session-start.md
├── claude-hooks
│   ├── config.json
│   ├── config.template.json
│   ├── CONFIGURATION.md
│   ├── core
│   │   ├── memory-retrieval.js
│   │   ├── mid-conversation.js
│   │   ├── session-end.js
│   │   ├── session-start.js
│   │   └── topic-change.js
│   ├── debug-pattern-test.js
│   ├── install_claude_hooks_windows.ps1
│   ├── install_hooks.py
│   ├── memory-mode-controller.js
│   ├── MIGRATION.md
│   ├── README-NATURAL-TRIGGERS.md
│   ├── README-phase2.md
│   ├── README.md
│   ├── simple-test.js
│   ├── statusline.sh
│   ├── test-adaptive-weights.js
│   ├── test-dual-protocol-hook.js
│   ├── test-mcp-hook.js
│   ├── test-natural-triggers.js
│   ├── test-recency-scoring.js
│   ├── tests
│   │   ├── integration-test.js
│   │   ├── phase2-integration-test.js
│   │   ├── test-code-execution.js
│   │   ├── test-cross-session.json
│   │   ├── test-session-tracking.json
│   │   └── test-threading.json
│   ├── utilities
│   │   ├── adaptive-pattern-detector.js
│   │   ├── context-formatter.js
│   │   ├── context-shift-detector.js
│   │   ├── conversation-analyzer.js
│   │   ├── dynamic-context-updater.js
│   │   ├── git-analyzer.js
│   │   ├── mcp-client.js
│   │   ├── memory-client.js
│   │   ├── memory-scorer.js
│   │   ├── performance-manager.js
│   │   ├── project-detector.js
│   │   ├── session-tracker.js
│   │   ├── tiered-conversation-monitor.js
│   │   └── version-checker.js
│   └── WINDOWS-SESSIONSTART-BUG.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Development-Sprint-November-2025.md
├── docs
│   ├── amp-cli-bridge.md
│   ├── api
│   │   ├── code-execution-interface.md
│   │   ├── memory-metadata-api.md
│   │   ├── PHASE1_IMPLEMENTATION_SUMMARY.md
│   │   ├── PHASE2_IMPLEMENTATION_SUMMARY.md
│   │   ├── PHASE2_REPORT.md
│   │   └── tag-standardization.md
│   ├── architecture
│   │   ├── search-enhancement-spec.md
│   │   └── search-examples.md
│   ├── architecture.md
│   ├── archive
│   │   └── obsolete-workflows
│   │       ├── load_memory_context.md
│   │       └── README.md
│   ├── assets
│   │   └── images
│   │       ├── dashboard-v3.3.0-preview.png
│   │       ├── memory-awareness-hooks-example.png
│   │       ├── project-infographic.svg
│   │       └── README.md
│   ├── CLAUDE_CODE_QUICK_REFERENCE.md
│   ├── cloudflare-setup.md
│   ├── deployment
│   │   ├── docker.md
│   │   ├── dual-service.md
│   │   ├── production-guide.md
│   │   └── systemd-service.md
│   ├── development
│   │   ├── ai-agent-instructions.md
│   │   ├── code-quality
│   │   │   ├── phase-2a-completion.md
│   │   │   ├── phase-2a-handle-get-prompt.md
│   │   │   ├── phase-2a-index.md
│   │   │   ├── phase-2a-install-package.md
│   │   │   └── phase-2b-session-summary.md
│   │   ├── code-quality-workflow.md
│   │   ├── dashboard-workflow.md
│   │   ├── issue-management.md
│   │   ├── pr-review-guide.md
│   │   ├── refactoring-notes.md
│   │   ├── release-checklist.md
│   │   └── todo-tracker.md
│   ├── docker-optimized-build.md
│   ├── document-ingestion.md
│   ├── DOCUMENTATION_AUDIT.md
│   ├── enhancement-roadmap-issue-14.md
│   ├── examples
│   │   ├── analysis-scripts.js
│   │   ├── maintenance-session-example.md
│   │   ├── memory-distribution-chart.jsx
│   │   └── tag-schema.json
│   ├── first-time-setup.md
│   ├── glama-deployment.md
│   ├── guides
│   │   ├── advanced-command-examples.md
│   │   ├── chromadb-migration.md
│   │   ├── commands-vs-mcp-server.md
│   │   ├── mcp-enhancements.md
│   │   ├── mdns-service-discovery.md
│   │   ├── memory-consolidation-guide.md
│   │   ├── migration.md
│   │   ├── scripts.md
│   │   └── STORAGE_BACKENDS.md
│   ├── HOOK_IMPROVEMENTS.md
│   ├── hooks
│   │   └── phase2-code-execution-migration.md
│   ├── http-server-management.md
│   ├── ide-compatability.md
│   ├── IMAGE_RETENTION_POLICY.md
│   ├── images
│   │   └── dashboard-placeholder.md
│   ├── implementation
│   │   ├── health_checks.md
│   │   └── performance.md
│   ├── IMPLEMENTATION_PLAN_HTTP_SSE.md
│   ├── integration
│   │   ├── homebrew.md
│   │   └── multi-client.md
│   ├── integrations
│   │   ├── gemini.md
│   │   ├── groq-bridge.md
│   │   ├── groq-integration-summary.md
│   │   └── groq-model-comparison.md
│   ├── integrations.md
│   ├── legacy
│   │   └── dual-protocol-hooks.md
│   ├── LM_STUDIO_COMPATIBILITY.md
│   ├── maintenance
│   │   └── memory-maintenance.md
│   ├── mastery
│   │   ├── api-reference.md
│   │   ├── architecture-overview.md
│   │   ├── configuration-guide.md
│   │   ├── local-setup-and-run.md
│   │   ├── testing-guide.md
│   │   └── troubleshooting.md
│   ├── migration
│   │   └── code-execution-api-quick-start.md
│   ├── natural-memory-triggers
│   │   ├── cli-reference.md
│   │   ├── installation-guide.md
│   │   └── performance-optimization.md
│   ├── oauth-setup.md
│   ├── pr-graphql-integration.md
│   ├── quick-setup-cloudflare-dual-environment.md
│   ├── README.md
│   ├── remote-configuration-wiki-section.md
│   ├── research
│   │   ├── code-execution-interface-implementation.md
│   │   └── code-execution-interface-summary.md
│   ├── ROADMAP.md
│   ├── sqlite-vec-backend.md
│   ├── statistics
│   │   ├── charts
│   │   │   ├── activity_patterns.png
│   │   │   ├── contributors.png
│   │   │   ├── growth_trajectory.png
│   │   │   ├── monthly_activity.png
│   │   │   └── october_sprint.png
│   │   ├── data
│   │   │   ├── activity_by_day.csv
│   │   │   ├── activity_by_hour.csv
│   │   │   ├── contributors.csv
│   │   │   └── monthly_activity.csv
│   │   ├── generate_charts.py
│   │   └── REPOSITORY_STATISTICS.md
│   ├── technical
│   │   ├── development.md
│   │   ├── memory-migration.md
│   │   ├── migration-log.md
│   │   ├── sqlite-vec-embedding-fixes.md
│   │   └── tag-storage.md
│   ├── testing
│   │   └── regression-tests.md
│   ├── testing-cloudflare-backend.md
│   ├── troubleshooting
│   │   ├── cloudflare-api-token-setup.md
│   │   ├── cloudflare-authentication.md
│   │   ├── general.md
│   │   ├── hooks-quick-reference.md
│   │   ├── pr162-schema-caching-issue.md
│   │   ├── session-end-hooks.md
│   │   └── sync-issues.md
│   └── tutorials
│       ├── advanced-techniques.md
│       ├── data-analysis.md
│       └── demo-session-walkthrough.md
├── examples
│   ├── claude_desktop_config_template.json
│   ├── claude_desktop_config_windows.json
│   ├── claude-desktop-http-config.json
│   ├── config
│   │   └── claude_desktop_config.json
│   ├── http-mcp-bridge.js
│   ├── memory_export_template.json
│   ├── README.md
│   ├── setup
│   │   └── setup_multi_client_complete.py
│   └── start_https_example.sh
├── install_service.py
├── install.py
├── LICENSE
├── NOTICE
├── pyproject.toml
├── pytest.ini
├── README.md
├── run_server.py
├── scripts
│   ├── .claude
│   │   └── settings.local.json
│   ├── archive
│   │   └── check_missing_timestamps.py
│   ├── backup
│   │   ├── backup_memories.py
│   │   ├── backup_sqlite_vec.sh
│   │   ├── export_distributable_memories.sh
│   │   └── restore_memories.py
│   ├── benchmarks
│   │   ├── benchmark_code_execution_api.py
│   │   ├── benchmark_hybrid_sync.py
│   │   └── benchmark_server_caching.py
│   ├── database
│   │   ├── analyze_sqlite_vec_db.py
│   │   ├── check_sqlite_vec_status.py
│   │   ├── db_health_check.py
│   │   └── simple_timestamp_check.py
│   ├── development
│   │   ├── debug_server_initialization.py
│   │   ├── find_orphaned_files.py
│   │   ├── fix_mdns.sh
│   │   ├── fix_sitecustomize.py
│   │   ├── remote_ingest.sh
│   │   ├── setup-git-merge-drivers.sh
│   │   ├── uv-lock-merge.sh
│   │   └── verify_hybrid_sync.py
│   ├── hooks
│   │   └── pre-commit
│   ├── installation
│   │   ├── install_linux_service.py
│   │   ├── install_macos_service.py
│   │   ├── install_uv.py
│   │   ├── install_windows_service.py
│   │   ├── install.py
│   │   ├── setup_backup_cron.sh
│   │   ├── setup_claude_mcp.sh
│   │   └── setup_cloudflare_resources.py
│   ├── linux
│   │   ├── service_status.sh
│   │   ├── start_service.sh
│   │   ├── stop_service.sh
│   │   ├── uninstall_service.sh
│   │   └── view_logs.sh
│   ├── maintenance
│   │   ├── assign_memory_types.py
│   │   ├── check_memory_types.py
│   │   ├── cleanup_corrupted_encoding.py
│   │   ├── cleanup_memories.py
│   │   ├── cleanup_organize.py
│   │   ├── consolidate_memory_types.py
│   │   ├── consolidation_mappings.json
│   │   ├── delete_orphaned_vectors_fixed.py
│   │   ├── fast_cleanup_duplicates_with_tracking.sh
│   │   ├── find_all_duplicates.py
│   │   ├── find_cloudflare_duplicates.py
│   │   ├── find_duplicates.py
│   │   ├── memory-types.md
│   │   ├── README.md
│   │   ├── recover_timestamps_from_cloudflare.py
│   │   ├── regenerate_embeddings.py
│   │   ├── repair_malformed_tags.py
│   │   ├── repair_memories.py
│   │   ├── repair_sqlite_vec_embeddings.py
│   │   ├── repair_zero_embeddings.py
│   │   ├── restore_from_json_export.py
│   │   └── scan_todos.sh
│   ├── migration
│   │   ├── cleanup_mcp_timestamps.py
│   │   ├── legacy
│   │   │   └── migrate_chroma_to_sqlite.py
│   │   ├── mcp-migration.py
│   │   ├── migrate_sqlite_vec_embeddings.py
│   │   ├── migrate_storage.py
│   │   ├── migrate_tags.py
│   │   ├── migrate_timestamps.py
│   │   ├── migrate_to_cloudflare.py
│   │   ├── migrate_to_sqlite_vec.py
│   │   ├── migrate_v5_enhanced.py
│   │   ├── TIMESTAMP_CLEANUP_README.md
│   │   └── verify_mcp_timestamps.py
│   ├── pr
│   │   ├── amp_collect_results.sh
│   │   ├── amp_detect_breaking_changes.sh
│   │   ├── amp_generate_tests.sh
│   │   ├── amp_pr_review.sh
│   │   ├── amp_quality_gate.sh
│   │   ├── amp_suggest_fixes.sh
│   │   ├── auto_review.sh
│   │   ├── detect_breaking_changes.sh
│   │   ├── generate_tests.sh
│   │   ├── lib
│   │   │   └── graphql_helpers.sh
│   │   ├── quality_gate.sh
│   │   ├── resolve_threads.sh
│   │   ├── run_pyscn_analysis.sh
│   │   ├── run_quality_checks.sh
│   │   ├── thread_status.sh
│   │   └── watch_reviews.sh
│   ├── quality
│   │   ├── fix_dead_code_install.sh
│   │   ├── phase1_dead_code_analysis.md
│   │   ├── phase2_complexity_analysis.md
│   │   ├── README_PHASE1.md
│   │   ├── README_PHASE2.md
│   │   ├── track_pyscn_metrics.sh
│   │   └── weekly_quality_review.sh
│   ├── README.md
│   ├── run
│   │   ├── run_mcp_memory.sh
│   │   ├── run-with-uv.sh
│   │   └── start_sqlite_vec.sh
│   ├── run_memory_server.py
│   ├── server
│   │   ├── check_http_server.py
│   │   ├── check_server_health.py
│   │   ├── memory_offline.py
│   │   ├── preload_models.py
│   │   ├── run_http_server.py
│   │   ├── run_memory_server.py
│   │   ├── start_http_server.bat
│   │   └── start_http_server.sh
│   ├── service
│   │   ├── deploy_dual_services.sh
│   │   ├── install_http_service.sh
│   │   ├── mcp-memory-http.service
│   │   ├── mcp-memory.service
│   │   ├── memory_service_manager.sh
│   │   ├── service_control.sh
│   │   ├── service_utils.py
│   │   └── update_service.sh
│   ├── sync
│   │   ├── check_drift.py
│   │   ├── claude_sync_commands.py
│   │   ├── export_memories.py
│   │   ├── import_memories.py
│   │   ├── litestream
│   │   │   ├── apply_local_changes.sh
│   │   │   ├── enhanced_memory_store.sh
│   │   │   ├── init_staging_db.sh
│   │   │   ├── io.litestream.replication.plist
│   │   │   ├── manual_sync.sh
│   │   │   ├── memory_sync.sh
│   │   │   ├── pull_remote_changes.sh
│   │   │   ├── push_to_remote.sh
│   │   │   ├── README.md
│   │   │   ├── resolve_conflicts.sh
│   │   │   ├── setup_local_litestream.sh
│   │   │   ├── setup_remote_litestream.sh
│   │   │   ├── staging_db_init.sql
│   │   │   ├── stash_local_changes.sh
│   │   │   ├── sync_from_remote_noconfig.sh
│   │   │   └── sync_from_remote.sh
│   │   ├── README.md
│   │   ├── safe_cloudflare_update.sh
│   │   ├── sync_memory_backends.py
│   │   └── sync_now.py
│   ├── testing
│   │   ├── run_complete_test.py
│   │   ├── run_memory_test.sh
│   │   ├── simple_test.py
│   │   ├── test_cleanup_logic.py
│   │   ├── test_cloudflare_backend.py
│   │   ├── test_docker_functionality.py
│   │   ├── test_installation.py
│   │   ├── test_mdns.py
│   │   ├── test_memory_api.py
│   │   ├── test_memory_simple.py
│   │   ├── test_migration.py
│   │   ├── test_search_api.py
│   │   ├── test_sqlite_vec_embeddings.py
│   │   ├── test_sse_events.py
│   │   ├── test-connection.py
│   │   └── test-hook.js
│   ├── utils
│   │   ├── claude_commands_utils.py
│   │   ├── generate_personalized_claude_md.sh
│   │   ├── groq
│   │   ├── groq_agent_bridge.py
│   │   ├── list-collections.py
│   │   ├── memory_wrapper_uv.py
│   │   ├── query_memories.py
│   │   ├── smithery_wrapper.py
│   │   ├── test_groq_bridge.sh
│   │   └── uv_wrapper.py
│   └── validation
│       ├── check_dev_setup.py
│       ├── check_documentation_links.py
│       ├── diagnose_backend_config.py
│       ├── validate_configuration_complete.py
│       ├── validate_memories.py
│       ├── validate_migration.py
│       ├── validate_timestamp_integrity.py
│       ├── verify_environment.py
│       ├── verify_pytorch_windows.py
│       └── verify_torch.py
├── SECURITY.md
├── selective_timestamp_recovery.py
├── SPONSORS.md
├── src
│   └── mcp_memory_service
│       ├── __init__.py
│       ├── api
│       │   ├── __init__.py
│       │   ├── client.py
│       │   ├── operations.py
│       │   ├── sync_wrapper.py
│       │   └── types.py
│       ├── backup
│       │   ├── __init__.py
│       │   └── scheduler.py
│       ├── cli
│       │   ├── __init__.py
│       │   ├── ingestion.py
│       │   ├── main.py
│       │   └── utils.py
│       ├── config.py
│       ├── consolidation
│       │   ├── __init__.py
│       │   ├── associations.py
│       │   ├── base.py
│       │   ├── clustering.py
│       │   ├── compression.py
│       │   ├── consolidator.py
│       │   ├── decay.py
│       │   ├── forgetting.py
│       │   ├── health.py
│       │   └── scheduler.py
│       ├── dependency_check.py
│       ├── discovery
│       │   ├── __init__.py
│       │   ├── client.py
│       │   └── mdns_service.py
│       ├── embeddings
│       │   ├── __init__.py
│       │   └── onnx_embeddings.py
│       ├── ingestion
│       │   ├── __init__.py
│       │   ├── base.py
│       │   ├── chunker.py
│       │   ├── csv_loader.py
│       │   ├── json_loader.py
│       │   ├── pdf_loader.py
│       │   ├── registry.py
│       │   ├── semtools_loader.py
│       │   └── text_loader.py
│       ├── lm_studio_compat.py
│       ├── mcp_server.py
│       ├── models
│       │   ├── __init__.py
│       │   └── memory.py
│       ├── server.py
│       ├── services
│       │   ├── __init__.py
│       │   └── memory_service.py
│       ├── storage
│       │   ├── __init__.py
│       │   ├── base.py
│       │   ├── cloudflare.py
│       │   ├── factory.py
│       │   ├── http_client.py
│       │   ├── hybrid.py
│       │   └── sqlite_vec.py
│       ├── sync
│       │   ├── __init__.py
│       │   ├── exporter.py
│       │   ├── importer.py
│       │   └── litestream_config.py
│       ├── utils
│       │   ├── __init__.py
│       │   ├── cache_manager.py
│       │   ├── content_splitter.py
│       │   ├── db_utils.py
│       │   ├── debug.py
│       │   ├── document_processing.py
│       │   ├── gpu_detection.py
│       │   ├── hashing.py
│       │   ├── http_server_manager.py
│       │   ├── port_detection.py
│       │   ├── system_detection.py
│       │   └── time_parser.py
│       └── web
│           ├── __init__.py
│           ├── api
│           │   ├── __init__.py
│           │   ├── analytics.py
│           │   ├── backup.py
│           │   ├── consolidation.py
│           │   ├── documents.py
│           │   ├── events.py
│           │   ├── health.py
│           │   ├── manage.py
│           │   ├── mcp.py
│           │   ├── memories.py
│           │   ├── search.py
│           │   └── sync.py
│           ├── app.py
│           ├── dependencies.py
│           ├── oauth
│           │   ├── __init__.py
│           │   ├── authorization.py
│           │   ├── discovery.py
│           │   ├── middleware.py
│           │   ├── models.py
│           │   ├── registration.py
│           │   └── storage.py
│           ├── sse.py
│           └── static
│               ├── app.js
│               ├── index.html
│               ├── README.md
│               ├── sse_test.html
│               └── style.css
├── start_http_debug.bat
├── start_http_server.sh
├── test_document.txt
├── test_version_checker.js
├── tests
│   ├── __init__.py
│   ├── api
│   │   ├── __init__.py
│   │   ├── test_compact_types.py
│   │   └── test_operations.py
│   ├── bridge
│   │   ├── mock_responses.js
│   │   ├── package-lock.json
│   │   ├── package.json
│   │   └── test_http_mcp_bridge.js
│   ├── conftest.py
│   ├── consolidation
│   │   ├── __init__.py
│   │   ├── conftest.py
│   │   ├── test_associations.py
│   │   ├── test_clustering.py
│   │   ├── test_compression.py
│   │   ├── test_consolidator.py
│   │   ├── test_decay.py
│   │   └── test_forgetting.py
│   ├── contracts
│   │   └── api-specification.yml
│   ├── integration
│   │   ├── package-lock.json
│   │   ├── package.json
│   │   ├── test_api_key_fallback.py
│   │   ├── test_api_memories_chronological.py
│   │   ├── test_api_tag_time_search.py
│   │   ├── test_api_with_memory_service.py
│   │   ├── test_bridge_integration.js
│   │   ├── test_cli_interfaces.py
│   │   ├── test_cloudflare_connection.py
│   │   ├── test_concurrent_clients.py
│   │   ├── test_data_serialization_consistency.py
│   │   ├── test_http_server_startup.py
│   │   ├── test_mcp_memory.py
│   │   ├── test_mdns_integration.py
│   │   ├── test_oauth_basic_auth.py
│   │   ├── test_oauth_flow.py
│   │   ├── test_server_handlers.py
│   │   └── test_store_memory.py
│   ├── performance
│   │   ├── test_background_sync.py
│   │   └── test_hybrid_live.py
│   ├── README.md
│   ├── smithery
│   │   └── test_smithery.py
│   ├── sqlite
│   │   └── simple_sqlite_vec_test.py
│   ├── test_client.py
│   ├── test_content_splitting.py
│   ├── test_database.py
│   ├── test_hybrid_cloudflare_limits.py
│   ├── test_hybrid_storage.py
│   ├── test_memory_ops.py
│   ├── test_semantic_search.py
│   ├── test_sqlite_vec_storage.py
│   ├── test_time_parser.py
│   ├── test_timestamp_preservation.py
│   ├── timestamp
│   │   ├── test_hook_vs_manual_storage.py
│   │   ├── test_issue99_final_validation.py
│   │   ├── test_search_retrieval_inconsistency.py
│   │   ├── test_timestamp_issue.py
│   │   └── test_timestamp_simple.py
│   └── unit
│       ├── conftest.py
│       ├── test_cloudflare_storage.py
│       ├── test_csv_loader.py
│       ├── test_fastapi_dependencies.py
│       ├── test_import.py
│       ├── test_json_loader.py
│       ├── test_mdns_simple.py
│       ├── test_mdns.py
│       ├── test_memory_service.py
│       ├── test_memory.py
│       ├── test_semtools_loader.py
│       ├── test_storage_interface_compatibility.py
│       └── test_tag_time_filtering.py
├── tools
│   ├── docker
│   │   ├── DEPRECATED.md
│   │   ├── docker-compose.http.yml
│   │   ├── docker-compose.pythonpath.yml
│   │   ├── docker-compose.standalone.yml
│   │   ├── docker-compose.uv.yml
│   │   ├── docker-compose.yml
│   │   ├── docker-entrypoint-persistent.sh
│   │   ├── docker-entrypoint-unified.sh
│   │   ├── docker-entrypoint.sh
│   │   ├── Dockerfile
│   │   ├── Dockerfile.glama
│   │   ├── Dockerfile.slim
│   │   ├── README.md
│   │   └── test-docker-modes.sh
│   └── README.md
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/src/mcp_memory_service/web/static/app.js:
--------------------------------------------------------------------------------

```javascript
/**
 * MCP Memory Service Dashboard - Main Application
 * Interactive frontend for memory management with real-time updates
 */

console.log('⚡ app.js loading - TOP OF FILE');

class MemoryDashboard {
    // Delay between individual file uploads to avoid overwhelming the server (ms)
    static INDIVIDUAL_UPLOAD_DELAY = 500;

    // Static configuration for settings modal system information
    static SYSTEM_INFO_CONFIG = {
        settingsVersion: {
            sources: [{ path: 'version', api: 'health' }],
            formatter: (value) => value || 'N/A'
        },
        settingsBackend: {
            sources: [
                { path: 'storage.storage_backend', api: 'detailedHealth' },
                { path: 'storage.backend', api: 'detailedHealth' }
            ],
            formatter: (value) => value || 'N/A'
        },
        settingsPrimaryBackend: {
            sources: [
                { path: 'storage.primary_backend', api: 'detailedHealth' },
                { path: 'storage.backend', api: 'detailedHealth' }
            ],
            formatter: (value) => value || 'N/A'
        },
        settingsEmbeddingModel: {
            sources: [
                { path: 'storage.primary_stats.embedding_model', api: 'detailedHealth' },
                { path: 'storage.embedding_model', api: 'detailedHealth' }
            ],
            formatter: (value) => value || 'N/A'
        },
        settingsEmbeddingDim: {
            sources: [
                { path: 'storage.primary_stats.embedding_dimension', api: 'detailedHealth' },
                { path: 'storage.embedding_dimension', api: 'detailedHealth' }
            ],
            formatter: (value) => value || 'N/A'
        },
        settingsDbSize: {
            sources: [
                { path: 'storage.primary_stats.database_size_mb', api: 'detailedHealth' },
                { path: 'storage.database_size_mb', api: 'detailedHealth' }
            ],
            formatter: (value) => (value != null) ? `${value.toFixed(2)} MB` : 'N/A'
        },
        settingsTotalMemories: {
            sources: [{ path: 'storage.total_memories', api: 'detailedHealth' }],
            formatter: (value) => (value != null) ? value.toLocaleString() : 'N/A'
        },
        settingsUptime: {
            sources: [{ path: 'uptime_seconds', api: 'detailedHealth' }],
            formatter: (value) => (value != null) ? MemoryDashboard.formatUptime(value) : 'N/A'
        }
    };

    constructor() {
        this.apiBase = '/api';
        this.eventSource = null;
        this.memories = [];
        this.currentView = 'dashboard';
        this.searchResults = [];
        this.isLoading = false;
        this.liveSearchEnabled = true;
        this.debounceTimer = null;

        // Settings with defaults
        this.settings = {
            theme: 'light',
            viewDensity: 'comfortable',
            previewLines: 3
        };

        // Documents upload state
        this.selectedFiles = [];
        this.documentsListenersSetup = false;
        this.processingMode = 'batch'; // 'batch' or 'individual'

        // Bind methods
        this.handleSearch = this.handleSearch.bind(this);
        this.handleQuickSearch = this.handleQuickSearch.bind(this);
        this.handleNavigation = this.handleNavigation.bind(this);
        this.handleAddMemory = this.handleAddMemory.bind(this);
        this.handleMemoryClick = this.handleMemoryClick.bind(this);

        this.init();
    }

    /**
     * Initialize the application
     */
    async init() {
        this.loadSettings();
        this.applyTheme();
        this.setupEventListeners();
        this.setupSSE();
        await this.loadVersion();
        await this.loadDashboardData();
        this.updateConnectionStatus('connected');

        // Initialize sync status monitoring for hybrid mode
        await this.checkSyncStatus();
        this.startSyncStatusMonitoring();
    }

    /**
     * Set up event listeners for UI interactions
     */
    setupEventListeners() {
        console.log('⚡ setupEventListeners() called');
        // Navigation
        document.querySelectorAll('.nav-item').forEach(item => {
            item.addEventListener('click', this.handleNavigation);
        });

        // Search functionality
        const quickSearch = document.getElementById('quickSearch');
        const searchBtn = document.querySelector('.search-btn');

        if (quickSearch) {
            quickSearch.addEventListener('input', this.debounce(this.handleQuickSearch, 300));
            quickSearch.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    this.handleSearch(e.target.value);
                }
            });
        }

        if (searchBtn && quickSearch) {
            searchBtn.addEventListener('click', () => {
                this.handleSearch(quickSearch.value);
            });
        }

        // Add memory functionality
        const addMemoryBtn = document.getElementById('addMemoryBtn');
        if (addMemoryBtn) {
            addMemoryBtn.addEventListener('click', this.handleAddMemory);
        }
        document.querySelectorAll('[data-action="add-memory"]').forEach(btn => {
            btn.addEventListener('click', this.handleAddMemory);
        });

        // Modal close handlers
        document.querySelectorAll('.modal-close').forEach(btn => {
            btn.addEventListener('click', (e) => {
                this.closeModal(e.target.closest('.modal-overlay'));
            });
        });

        // Modal overlay click to close
        document.querySelectorAll('.modal-overlay').forEach(overlay => {
            overlay.addEventListener('click', (e) => {
                if (e.target === overlay) {
                    this.closeModal(overlay);
                }
            });
        });

        // Add memory form submission
        const saveMemoryBtn = document.getElementById('saveMemoryBtn');
        if (saveMemoryBtn) {
            saveMemoryBtn.addEventListener('click', this.handleSaveMemory.bind(this));
        }

        const cancelAddBtn = document.getElementById('cancelAddBtn');
        if (cancelAddBtn) {
            cancelAddBtn.addEventListener('click', () => {
                this.closeModal(document.getElementById('addMemoryModal'));
            });
        }

        // Quick action handlers
        document.querySelectorAll('.action-card').forEach(card => {
            card.addEventListener('click', (e) => {
                const action = e.currentTarget.dataset.action;
                this.handleQuickAction(action);
            });
        });

        // Live search toggle handler
        const liveSearchToggle = document.getElementById('liveSearchToggle');
        liveSearchToggle?.addEventListener('change', this.handleLiveSearchToggle.bind(this));

        // Filter handlers for search view
        const tagFilterInput = document.getElementById('tagFilter');
        tagFilterInput?.addEventListener('input', this.handleDebouncedFilterChange.bind(this));
        tagFilterInput?.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                this.handleFilterChange();
            }
        });
        document.getElementById('dateFilter')?.addEventListener('change', this.handleFilterChange.bind(this));
        document.getElementById('typeFilter')?.addEventListener('change', this.handleFilterChange.bind(this));

        // View option handlers
        document.querySelectorAll('.view-btn').forEach(btn => {
            btn.addEventListener('click', (e) => {
                this.handleViewModeChange(e.target.dataset.view);
            });
        });

        // New filter action handlers
        document.getElementById('applyFiltersBtn')?.addEventListener('click', this.handleFilterChange.bind(this));
        document.getElementById('clearFiltersBtn')?.addEventListener('click', this.clearAllFilters.bind(this));

        // Theme toggle button
        document.getElementById('themeToggleBtn')?.addEventListener('click', () => {
            this.toggleTheme();
        });

        // Settings button
        document.getElementById('settingsBtn')?.addEventListener('click', () => {
            this.openSettingsModal();
        });

        // Settings modal handlers
        document.getElementById('saveSettingsBtn')?.addEventListener('click', () => {
            this.saveSettings();
        });

        document.getElementById('cancelSettingsBtn')?.addEventListener('click', () => {
            this.closeModal(document.getElementById('settingsModal'));
        });

        // Tag cloud event delegation
        document.getElementById('tagsCloudContainer')?.addEventListener('click', (e) => {
            if (e.target.classList.contains('tag-bubble') || e.target.closest('.tag-bubble')) {
                const tagButton = e.target.classList.contains('tag-bubble') ? e.target : e.target.closest('.tag-bubble');
                const tag = tagButton.dataset.tag;
                if (tag) {
                    this.filterByTag(tag);
                }
            }
        });

        // Manage tab event listeners
        document.getElementById('deleteByTagBtn')?.addEventListener('click', this.handleBulkDeleteByTag.bind(this));
        document.getElementById('cleanupDuplicatesBtn')?.addEventListener('click', this.handleCleanupDuplicates.bind(this));
        document.getElementById('deleteByDateBtn')?.addEventListener('click', this.handleBulkDeleteByDate.bind(this));
        document.getElementById('optimizeDbBtn')?.addEventListener('click', this.handleOptimizeDatabase.bind(this));
        document.getElementById('rebuildIndexBtn')?.addEventListener('click', this.handleRebuildIndex.bind(this));

        // Analytics tab event listeners
        document.getElementById('growthPeriodSelect')?.addEventListener('change', this.handleGrowthPeriodChange.bind(this));
        document.getElementById('heatmapPeriodSelect')?.addEventListener('change', this.handleHeatmapPeriodChange.bind(this));
        document.getElementById('topTagsPeriodSelect')?.addEventListener('change', this.handleTopTagsPeriodChange.bind(this));
        document.getElementById('activityGranularitySelect')?.addEventListener('change', this.handleActivityGranularityChange.bind(this));

        // Keyboard shortcuts
        document.addEventListener('keydown', (e) => {
            if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
                e.preventDefault();
                document.getElementById('searchInput').focus();
            }
            if ((e.ctrlKey || e.metaKey) && e.key === 'm') {
                e.preventDefault();
                this.handleAddMemory();
            }
        });
    }

    /**
     * Set up Server-Sent Events for real-time updates
     */
    setupSSE() {
        try {
            this.eventSource = new EventSource(`${this.apiBase}/events`);

            this.eventSource.onopen = () => {
                this.updateConnectionStatus('connected');
            };

            this.eventSource.onmessage = (event) => {
                try {
                    const data = JSON.parse(event.data);
                    this.handleRealtimeUpdate(data);
                } catch (error) {
                    console.error('Error parsing SSE data:', error);
                }
            };

            // Add specific event listeners for sync progress
            this.eventSource.addEventListener('sync_progress', (event) => {
                try {
                    const data = JSON.parse(event.data);
                    this.handleSyncProgress(data);
                } catch (error) {
                    console.error('Error parsing sync_progress event:', error);
                }
            });

            this.eventSource.addEventListener('sync_completed', (event) => {
                try {
                    const data = JSON.parse(event.data);
                    this.handleSyncCompleted(data);
                } catch (error) {
                    console.error('Error parsing sync_completed event:', error);
                }
            });

            this.eventSource.onerror = (error) => {
                console.error('SSE connection error:', error);
                this.updateConnectionStatus('disconnected');

                // Attempt to reconnect after 5 seconds
                setTimeout(() => {
                    if (this.eventSource.readyState === EventSource.CLOSED) {
                        this.setupSSE();
                    }
                }, 5000);
            };

        } catch (error) {
            console.error('Failed to establish SSE connection:', error);
            this.updateConnectionStatus('disconnected');
        }
    }

    /**
     * Handle real-time updates from SSE
     */
    handleRealtimeUpdate(data) {
        switch (data.type) {
            case 'memory_added':
                this.handleMemoryAdded(data.memory);
                this.showToast('Memory added successfully', 'success');
                break;
            case 'memory_deleted':
                this.handleMemoryDeleted(data.memory_id);
                this.showToast('Memory deleted', 'success');
                break;
            case 'memory_updated':
                this.handleMemoryUpdated(data.memory);
                this.showToast('Memory updated', 'success');
                break;
            case 'stats_updated':
                this.updateDashboardStats(data.stats);
                break;
            default:
                // Unknown event type - ignore silently
        }
    }

    /**
     * Handle sync progress updates from SSE
     */
    handleSyncProgress(data) {
        console.log('Sync progress:', data);

        // Update sync status display if visible
        const syncStatus = document.getElementById('syncStatus');
        if (syncStatus) {
            const progressText = `Syncing: ${data.synced_count}/${data.total_count} (${data.progress_percentage}%)`;
            syncStatus.textContent = progressText;
            syncStatus.className = 'sync-status syncing';
        }

        // Update memory count in real-time if on dashboard
        if (this.currentView === 'dashboard') {
            const memoryCountElement = document.getElementById('totalMemories');
            if (memoryCountElement && data.synced_count) {
                // Refresh the detailed health to get accurate count
                this.loadDashboardData().catch(err => console.error('Error refreshing dashboard:', err));
            }
        }

        // Show toast notification for manual sync
        if (data.sync_type === 'manual') {
            this.showToast(data.message || `Syncing: ${data.synced_count}/${data.total_count}`, 'info');
        }
    }

    /**
     * Handle sync completion from SSE
     */
    handleSyncCompleted(data) {
        console.log('Sync completed:', data);

        // Update sync status display
        const syncStatus = document.getElementById('syncStatus');
        if (syncStatus) {
            syncStatus.textContent = 'Synced';
            syncStatus.className = 'sync-status synced';
        }

        // Refresh dashboard data to show updated counts
        if (this.currentView === 'dashboard') {
            this.loadDashboardData().catch(err => console.error('Error refreshing dashboard:', err));
        }

        // Also refresh sync status for hybrid mode
        this.checkSyncStatus().catch(err => console.error('Error checking sync status:', err));

        // Show completion notification
        const message = data.message || `Sync completed: ${data.synced_count} memories synced`;
        this.showToast(message, 'success');
    }

    /**
     * Load application version from health endpoint
     */
    async loadVersion() {
        try {
            const healthResponse = await this.apiCall('/health');
            const versionBadge = document.getElementById('versionBadge');
            if (versionBadge && healthResponse.version) {
                versionBadge.textContent = `v${healthResponse.version}`;
            }
        } catch (error) {
            console.error('Error loading version:', error);
            const versionBadge = document.getElementById('versionBadge');
            if (versionBadge) {
                versionBadge.textContent = 'v?.?.?';
            }
        }
    }

    /**
     * Load initial dashboard data
     */
    async loadDashboardData() {
        this.setLoading(true);

        try {
            // Load recent memories for dashboard display
            const memoriesResponse = await this.apiCall('/memories?page=1&page_size=100');
            if (memoriesResponse.memories) {
                this.memories = memoriesResponse.memories;
                this.renderRecentMemories(memoriesResponse.memories);
            }

            // Load basic statistics
            const statsResponse = await this.apiCall('/health/detailed');
            if (statsResponse.storage) {
                this.updateDashboardStats(statsResponse.storage);
            }


        } catch (error) {
            console.error('Error loading dashboard data:', error);
            this.showToast('Failed to load dashboard data', 'error');
        } finally {
            this.setLoading(false);
        }
    }

    /**
     * Load browse view data (tags)
     */
    async loadBrowseData() {
        this.setLoading(true);
        try {
            // Load tags with counts from the dedicated endpoint
            const tagsResponse = await this.apiCall('/tags');
            if (tagsResponse.tags) {
                this.tags = tagsResponse.tags;
                this.renderTagsCloud();
            }
        } catch (error) {
            console.error('Error loading browse data:', error);
            this.showToast('Failed to load browse data', 'error');
        } finally {
            this.setLoading(false);
        }
    }

    /**
     * Load documents view data
     */
    async loadDocumentsData() {
        this.setLoading(true);
        try {
            // Load upload history
            await this.loadUploadHistory();
            // Setup document upload event listeners
            this.setupDocumentsEventListeners();
        } catch (error) {
            console.error('Error loading documents data:', error);
            this.showToast('Failed to load documents data', 'error');
        } finally {
            this.setLoading(false);
        }
    }

    /**
     * Load upload history from API
     */
    async loadUploadHistory() {
        console.log('Loading upload history...');
        try {
            const historyResponse = await this.apiCall('/documents/history');
            console.log('Upload history response:', historyResponse);
            if (historyResponse.uploads) {
                this.renderUploadHistory(historyResponse.uploads);
            } else {
                console.warn('No uploads property in response');
                this.renderUploadHistory([]);
            }
        } catch (error) {
            console.error('Error loading upload history:', error);
            // Show a message in the history container instead of just logging
            const historyContainer = document.getElementById('uploadHistory');
            if (historyContainer) {
                historyContainer.innerHTML = '<p style="text-align: center; color: var(--error);">Failed to load upload history. Please check the console for details.</p>';
            }
        }
    }

    /**
     * Render upload history
     */
    renderUploadHistory(uploads) {
        const historyContainer = document.getElementById('uploadHistory');
        if (!historyContainer) return;

        if (uploads.length === 0) {
            historyContainer.innerHTML = '<p style="text-align: center; color: var(--neutral-500);">No uploads yet. Start by uploading some documents!</p>';
            return;
        }

        const historyHtml = uploads.map(upload => {
        const statusClass = upload.status.toLowerCase();
        const statusText = upload.status.charAt(0).toUpperCase() + upload.status.slice(1);
        const progressPercent = upload.progress || 0;
        const hasMemories = upload.chunks_stored > 0;

        return `
        <div class="upload-item ${statusClass}" data-upload-id="${upload.upload_id}" data-filename="${this.escapeHtml(upload.filename)}">
        <div class="upload-info">
        <div class="upload-filename">${this.escapeHtml(upload.filename)}</div>
                        <div class="upload-meta">
                            ${upload.chunks_stored || 0} chunks stored •
                            ${(upload.file_size / 1024).toFixed(1)} KB •
                            ${new Date(upload.created_at).toLocaleString()}
                        </div>
                        ${upload.status === 'processing' ? `
                            <div class="progress-bar">
                                <div class="progress-fill" style="width: ${progressPercent}%"></div>
                            </div>
                        ` : ''}
                    </div>
                    <div class="upload-actions-container">
                        <div class="upload-status ${statusClass}">
                            <span>${statusText}</span>
                            ${upload.errors && upload.errors.length > 0 ? `
                                <span title="${this.escapeHtml(upload.errors.join('; '))}">⚠️</span>
                            ` : ''}
                        </div>
                        ${upload.status === 'completed' && hasMemories ? `
                            <div class="upload-actions">
                                <button class="btn-icon btn-view-memory"
                                title="View memory chunks">
                                    <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
                                        <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
                                    </svg>
                                    <span>View</span>
                                </button>
                                <button class="btn-icon btn-remove"
                                title="Remove document and memories">
                                    <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
                                        <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
                                    </svg>
                                    <span>Remove</span>
                                </button>
                            </div>
                        ` : ''}
                    </div>
                </div>
            `;
        }).join('');

        historyContainer.innerHTML = historyHtml;
    }

    /**
     * Setup event listeners for documents view
     */
    setupDocumentsEventListeners() {
        // Prevent duplicate event listener setup
        if (this.documentsListenersSetup) {
            console.log('Document listeners already set up, skipping...');
            return;
        }

        // File selection buttons
        const fileSelectBtn = document.getElementById('fileSelectBtn');
        const fileInput = document.getElementById('fileInput');

        if (fileSelectBtn && fileInput) {
            fileSelectBtn.addEventListener('click', () => {
                fileInput.click();
            });

            fileInput.addEventListener('change', (e) => {
                this.handleFileSelection(e.target.files);
            });
        }

        // Drag and drop
        const dropZone = document.getElementById('dropZone');
        if (dropZone) {
            dropZone.addEventListener('dragover', (e) => {
                e.preventDefault();
                dropZone.classList.add('drag-over');
            });

            dropZone.addEventListener('dragleave', (e) => {
                e.preventDefault();
                dropZone.classList.remove('drag-over');
            });

            dropZone.addEventListener('drop', (e) => {
                e.preventDefault();
                dropZone.classList.remove('drag-over');
                const files = e.dataTransfer.files;
                this.handleFileSelection(files);
            });
        }

        // Configuration controls - cache for performance
        this.chunkSizeInput = document.getElementById('chunkSize');
        this.chunkOverlapInput = document.getElementById('chunkOverlap');
        this.memoryTypeInput = document.getElementById('memoryType');
        const chunkSizeValue = document.getElementById('chunkSizeValue');
        const chunkOverlapValue = document.getElementById('chunkOverlapValue');

        if (this.chunkSizeInput && chunkSizeValue) {
            this.chunkSizeInput.addEventListener('input', (e) => {
                chunkSizeValue.textContent = e.target.value;
                this.updateUploadButton();
            });
        }

        // Chunking help info icon
        const infoIcon = document.querySelector('.info-icon');
        if (infoIcon) {
            infoIcon.addEventListener('click', () => {
                this.toggleChunkingHelp();
            });
        }

        // Overlap help info icon
        const overlapInfoIcon = document.querySelector('.info-icon-overlap');
        if (overlapInfoIcon) {
            overlapInfoIcon.addEventListener('click', () => {
                this.toggleOverlapHelp();
            });
        }

        // Processing mode help info icon
        const processingModeInfoIcon = document.querySelector('.info-icon-processing');
        if (processingModeInfoIcon) {
            processingModeInfoIcon.addEventListener('click', () => {
                this.toggleProcessingModeHelp();
            });
        }

        if (this.chunkOverlapInput && chunkOverlapValue) {
            this.chunkOverlapInput.addEventListener('input', (e) => {
                chunkOverlapValue.textContent = e.target.value;
                this.updateUploadButton();
            });
        }

        // Processing mode toggle buttons - cache for performance
        this.batchModeBtn = document.getElementById('batchModeBtn');
        this.individualModeBtn = document.getElementById('individualModeBtn');
        this.modeDescription = document.getElementById('modeDescription');

        if (this.batchModeBtn) {
            this.batchModeBtn.addEventListener('click', () => {
                this.setProcessingMode('batch');
            });
        }

        if (this.individualModeBtn) {
            this.individualModeBtn.addEventListener('click', () => {
                this.setProcessingMode('individual');
            });
        }

        // Upload button
        const uploadBtn = document.getElementById('uploadBtn');
        if (uploadBtn) {
            uploadBtn.addEventListener('click', () => {
                this.handleDocumentUpload();
            });
        }

        // Add event listeners for buttons with data-action attribute
        document.querySelectorAll('[data-action]').forEach(button => {
            button.addEventListener('click', (e) => {
                const action = e.currentTarget.dataset.action;
                if (this[action] && typeof this[action] === 'function') {
                    this[action]();
                }
            });
        });

        // Sync buttons event listeners are attached in checkSyncStatus()
        // after buttons are confirmed to be accessible in the DOM

        // Backup now button
        const backupNowButton = document.getElementById('backupNowButton');
        if (backupNowButton) {
            backupNowButton.addEventListener('click', () => {
                this.createBackup();
            });
        }

        // Document search button
        const docSearchBtn = document.getElementById('docSearchBtn');
        const docSearchInput = document.getElementById('docSearchInput');
        if (docSearchBtn && docSearchInput) {
            docSearchBtn.addEventListener('click', () => {
                const query = docSearchInput.value.trim();
                if (query) {
                    this.searchDocumentContent(query);
                } else {
                    this.showToast('Please enter a search query', 'warning');
                }
            });

            // Enter key to search
            docSearchInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    const query = docSearchInput.value.trim();
                    if (query) {
                        this.searchDocumentContent(query);
                    }
                }
            });
        }

        // Upload history action buttons (event delegation)
        const uploadHistory = document.getElementById('uploadHistory');
        if (uploadHistory) {
            uploadHistory.addEventListener('click', (e) => {
                const button = e.target.closest('.btn-view-memory, .btn-remove');
                if (!button) return;

                const uploadItem = button.closest('.upload-item');
                const uploadId = uploadItem?.dataset.uploadId;
                const filename = uploadItem?.dataset.filename;

                if (!uploadId) return;

                if (button.classList.contains('btn-view-memory')) {
                    this.viewDocumentMemory(uploadId);
                } else if (button.classList.contains('btn-remove')) {
                    this.removeDocument(uploadId, filename);
                }
            });
        }

        // Close modal when clicking outside
        const memoryViewerModal = document.getElementById('memoryViewerModal');
        if (memoryViewerModal) {
            memoryViewerModal.addEventListener('click', (e) => {
                if (e.target === memoryViewerModal) {
                    this.closeMemoryViewer();
                }
            });
        }

        // Mark listeners as set up to prevent duplicates
        this.documentsListenersSetup = true;
        console.log('Document listeners setup complete');
    }

    /**
     * Handle file selection from input or drag-drop
     */
    handleFileSelection(files) {
        if (!files || files.length === 0) return;

        this.selectedFiles = Array.from(files);
        this.updateUploadButton();

        // Show file preview in drop zone
        const dropZone = document.getElementById('dropZone');
        if (dropZone) {
            const fileNames = this.selectedFiles.map(f => this.escapeHtml(f.name)).join(', ');
            const content = dropZone.querySelector('.drop-zone-content');
            if (content) {
                content.innerHTML = `
                    <svg width="48" height="48" fill="currentColor" viewBox="0 0 24 24">
                        <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
                    </svg>
                    <h3>${files.length} file${files.length > 1 ? 's' : ''} selected</h3>
                    <p>${fileNames}</p>
                    <input type="file" id="fileInput" multiple accept=".pdf,.docx,.pptx,.txt,.md,.json" style="display: none;">
                `;
            }
        }
    }

    /**
     * Update upload button state based on selections
     */
    updateUploadButton() {
        const uploadBtn = document.getElementById('uploadBtn');
        const hasFiles = this.selectedFiles && this.selectedFiles.length > 0;

        if (uploadBtn) {
            uploadBtn.disabled = !hasFiles;
            uploadBtn.textContent = hasFiles ?
                `Upload & Ingest ${this.selectedFiles.length} file${this.selectedFiles.length > 1 ? 's' : ''}` :
                'Upload & Ingest';
        }

        // Show/hide processing mode section based on file count
        const processingModeSection = document.getElementById('processingModeSection');
        if (processingModeSection) {
            processingModeSection.style.display = (hasFiles && this.selectedFiles.length > 1) ? 'block' : 'none';
        }
    }

    /**
     * Set processing mode (batch or individual)
     */
    setProcessingMode(mode) {
        this.processingMode = mode;

        // Update button states (using cached DOM elements)
        if (this.batchModeBtn) {
            this.batchModeBtn.classList.toggle('active', mode === 'batch');
        }
        if (this.individualModeBtn) {
            this.individualModeBtn.classList.toggle('active', mode === 'individual');
        }
        if (this.modeDescription) {
            this.modeDescription.innerHTML = mode === 'batch'
                ? '<small>All selected files will be processed together with the same tags.</small>'
                : '<small>Each file will be processed individually with the same tags.</small>';
        }

        console.log(`Processing mode set to: ${mode}`);
    }

    /**
     * Handle document upload
     */
    async handleDocumentUpload() {
        if (!this.selectedFiles || this.selectedFiles.length === 0) {
            this.showToast('No files selected', 'error');
            return;
        }

        const tags = document.getElementById('docTags')?.value || '';
        const chunkSize = this.chunkSizeInput?.value || 1000;
        const chunkOverlap = this.chunkOverlapInput?.value || 200;
        const memoryType = this.memoryTypeInput?.value || 'document';

        try {
            this.setLoading(true);

            if (this.selectedFiles.length === 1 || this.processingMode === 'individual') {
                // Individual file processing (single file or individual mode for multiple files)
                for (let i = 0; i < this.selectedFiles.length; i++) {
                    const file = this.selectedFiles[i];
                    try {
                        await this.uploadSingleDocument(file, {
                            tags,
                            chunk_size: parseInt(chunkSize),
                            chunk_overlap: parseInt(chunkOverlap),
                            memory_type: memoryType
                        });

                        // Small delay between individual uploads to avoid overwhelming the server
                        if (i < this.selectedFiles.length - 1) {
                            await new Promise(resolve => setTimeout(resolve, this.constructor.INDIVIDUAL_UPLOAD_DELAY));
                        }
                    } catch (error) {
                        console.error(`Failed to upload ${file.name}:`, error);
                        this.showToast(`Failed to upload ${file.name}: ${error.message}`, 'error');
                        // Continue with remaining files
                    }
                }
            } else {
                // Batch upload
                await this.uploadBatchDocuments(this.selectedFiles, {
                    tags,
                    chunk_size: parseInt(chunkSize),
                    chunk_overlap: parseInt(chunkOverlap),
                    memory_type: memoryType
                });
            }

            // Clear selection and reload history
            this.selectedFiles = [];
            this.updateUploadButton();
            await this.loadUploadHistory();

            // Reset drop zone
            const dropZone = document.getElementById('dropZone');
            if (dropZone) {
                const content = dropZone.querySelector('.drop-zone-content');
                if (content) {
                    content.innerHTML = `
                        <svg width="48" height="48" fill="currentColor" viewBox="0 0 24 24">
                            <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
                        </svg>
                        <h3>Drag & drop files here</h3>
                        <p>or <button id="fileSelectBtn" class="link-button">browse to select files</button></p>
                        <p class="supported-formats">Supported formats: PDF, DOCX, PPTX, TXT, MD, JSON</p>
                        <input type="file" id="fileInput" multiple accept=".pdf,.docx,.pptx,.txt,.md,.json" style="display: none;">
                    `;
                }
                // Re-setup event listeners for the new elements
                this.setupDocumentsEventListeners();
            }

        } catch (error) {
            console.error('Upload error:', error);
            this.showToast('Upload failed: ' + error.message, 'error');
        } finally {
            this.setLoading(false);
        }
    }

    /**
     * Upload single document
     */
    async uploadSingleDocument(file, config) {
        console.log(`Uploading file: ${file.name}, size: ${file.size} bytes`);
        const formData = new FormData();
        formData.append('file', file);
        formData.append('tags', config.tags);
        formData.append('chunk_size', config.chunk_size.toString());
        formData.append('chunk_overlap', config.chunk_overlap.toString());
        formData.append('memory_type', config.memory_type);

        const response = await fetch(`${this.apiBase}/documents/upload`, {
            method: 'POST',
            body: formData
        });

        console.log(`Upload response status: ${response.status}`);

        if (!response.ok) {
            let errorMessage = `Upload failed with status ${response.status}`;
            try {
                const error = await response.json();
                console.error('Upload error details:', error);
                errorMessage = error.detail || error.message || errorMessage;
            } catch (e) {
                console.error('Could not parse error response:', e);
                try {
                    const errorText = await response.text();
                    console.error('Error response text:', errorText);
                    errorMessage = errorText || errorMessage;
                } catch (e2) {
                    console.error('Could not read error response:', e2);
                }
            }
            throw new Error(errorMessage);
        }

        const result = await response.json();
        console.log('Upload result:', result);
        this.showToast(`Upload started for ${file.name}`, 'success');

        // Monitor progress if we have an upload ID
        if (result.upload_id) {
            this.monitorUploadProgress(result.upload_id);
        }

        return result;
    }

    /**
     * Upload batch documents
     */
    async uploadBatchDocuments(files, config) {
        const formData = new FormData();
        files.forEach(file => {
            formData.append('files', file);
        });
        formData.append('tags', config.tags);
        formData.append('chunk_size', config.chunk_size.toString());
        formData.append('chunk_overlap', config.chunk_overlap.toString());
        formData.append('memory_type', config.memory_type);

        const response = await fetch(`${this.apiBase}/documents/batch-upload`, {
            method: 'POST',
            body: formData
        });

        if (!response.ok) {
            const error = await response.json();
            throw new Error(error.detail || 'Batch upload failed');
        }

        const result = await response.json();
        this.showToast(`Batch upload started for ${files.length} files`, 'success');

        // Monitor progress if we have an upload ID
        if (result.upload_id) {
            this.monitorUploadProgress(result.upload_id);
        }

        return result;
    }

    /**
     * Monitor upload progress by polling status endpoint
     */
    monitorUploadProgress(uploadId) {
        const pollStatus = async () => {
            try {
                const statusResponse = await this.apiCall(`/documents/status/${uploadId}`);
                this.updateUploadProgress(uploadId, statusResponse);

                if (statusResponse.progress >= 100 || statusResponse.status === 'completed' || statusResponse.status === 'failed') {
                    // Upload completed, refresh history
                    this.loadUploadHistory();
                } else {
                    // Continue polling
                    setTimeout(pollStatus, 2000); // Poll every 2 seconds
                }
            } catch (error) {
                // If polling fails, try again with longer interval
                setTimeout(pollStatus, 5000);
            }
        };

        // Start polling after a short delay
        setTimeout(pollStatus, 1000);
    }

    /**
     * Update upload progress display
     */
    updateUploadProgress(uploadId, statusData) {
        // Find the upload item in history and update it
        const historyContainer = document.getElementById('uploadHistory');
        if (!historyContainer) return;

        const uploadItems = historyContainer.querySelectorAll('.upload-item');
        uploadItems.forEach(item => {
            const filename = item.querySelector('.upload-filename');
            if (filename && filename.textContent.includes(uploadId)) {
                // This is a simplified update - in practice you'd match by upload ID
                this.loadUploadHistory(); // For now, just refresh the entire history
            }
        });
    }

    /**
     * Check hybrid backend sync status
     */
    async checkSyncStatus() {
        // Skip UI updates during force sync to prevent periodic polling from overwriting the syncing state
        if (this._isForceSyncing) {
            return;
        }

        try {
            const syncStatus = await this.apiCall('/sync/status');

            // Get compact sync control element
            const syncControl = document.getElementById('syncControl');
            if (!syncControl) {
                console.warn('Sync control element not found');
                return;
            }

            if (!syncStatus.is_hybrid) {
                syncControl.style.display = 'none';
                return;
            }

            // Show sync control for hybrid mode
            syncControl.style.display = 'block';

            // Update sync status UI elements
            const statusText = document.getElementById('syncStatusText');
            const syncProgress = document.getElementById('syncProgress');
            const pauseButton = document.getElementById('pauseSyncButton');
            const resumeButton = document.getElementById('resumeSyncButton');
            const syncButton = document.getElementById('forceSyncButton');

            // Update pause/resume button visibility based on running state
            const isPaused = syncStatus.is_paused || !syncStatus.is_running;

            // Attach event listeners if not already attached
            if (pauseButton && !pauseButton._listenerAttached) {
                pauseButton.addEventListener('click', () => {
                    this.pauseSync();
                });
                pauseButton._listenerAttached = true;
            }
            if (resumeButton && !resumeButton._listenerAttached) {
                resumeButton.addEventListener('click', () => {
                    this.resumeSync();
                });
                resumeButton._listenerAttached = true;
            }
            if (syncButton && !syncButton._listenerAttached) {
                syncButton.addEventListener('click', () => {
                    this.forceSync();
                });
                syncButton._listenerAttached = true;
            }

            if (pauseButton) {
                pauseButton.style.display = isPaused ? 'none' : 'flex';
            }
            if (resumeButton) {
                resumeButton.style.display = isPaused ? 'flex' : 'none';
            }

            // Determine status and update UI (dot color is handled by CSS classes)
            if (isPaused) {
                statusText.textContent = 'Paused';
                syncProgress.textContent = '';
                syncControl.className = 'sync-control-compact paused';
                if (syncButton) syncButton.disabled = true;
            } else if (syncStatus.status === 'syncing') {
                statusText.textContent = 'Syncing';
                syncProgress.textContent = syncStatus.operations_pending > 0 ? `${syncStatus.operations_pending} pending` : '';
                syncControl.className = 'sync-control-compact syncing';
                if (syncButton) syncButton.disabled = true;
            } else if (syncStatus.status === 'pending') {
                statusText.textContent = 'Pending';
                syncProgress.textContent = `${syncStatus.operations_pending} ops`;
                syncControl.className = 'sync-control-compact pending';
                if (syncButton) syncButton.disabled = false;
            } else if (syncStatus.status === 'error') {
                statusText.textContent = 'Error';
                syncProgress.textContent = `${syncStatus.operations_failed} failed`;
                syncControl.className = 'sync-control-compact error';
                if (syncButton) syncButton.disabled = false;
            } else {
                // synced status
                statusText.textContent = 'Synced';
                syncProgress.textContent = '';
                syncControl.className = 'sync-control-compact synced';
                if (syncButton) syncButton.disabled = false;
            }

        } catch (error) {
            console.error('Error checking sync status:', error);
            // Hide sync control on error (likely not hybrid mode)
            const syncControl = document.getElementById('syncControl');
            if (syncControl) syncControl.style.display = 'none';
        }
    }

    /**
     * Format time delta in human readable format
     */
    formatTimeDelta(seconds) {
        if (seconds < 60) return `${seconds}s ago`;
        if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
        if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
        return `${Math.floor(seconds / 86400)}d ago`;
    }

    /**
     * Pause background sync
     */
    async pauseSync() {
        try {
            const result = await this.apiCall('/sync/pause', 'POST');
            if (result.success) {
                this.showToast('Sync paused', 'success');

                // Update UI immediately using API response data
                const pauseButton = document.getElementById('pauseSyncButton');
                const resumeButton = document.getElementById('resumeSyncButton');
                const statusText = document.getElementById('syncStatusText');
                const syncControl = document.getElementById('syncControl');

                if (pauseButton) pauseButton.style.display = 'none';
                if (resumeButton) resumeButton.style.display = 'flex';
                if (statusText) statusText.textContent = 'Paused';
                if (syncControl) syncControl.className = 'sync-control-compact paused';

                // Small delay to allow backend state to propagate before checking status
                await new Promise(resolve => setTimeout(resolve, 200));
            } else {
                this.showToast('Failed to pause sync: ' + result.message, 'error');
            }
            await this.checkSyncStatus();
        } catch (error) {
            console.error('Error pausing sync:', error);
            this.showToast('Failed to pause sync', 'error');
        }
    }

    /**
     * Resume background sync
     */
    async resumeSync() {
        try {
            const result = await this.apiCall('/sync/resume', 'POST');
            if (result.success) {
                this.showToast('Sync resumed', 'success');

                // Update UI immediately using API response data
                const pauseButton = document.getElementById('pauseSyncButton');
                const resumeButton = document.getElementById('resumeSyncButton');
                const statusText = document.getElementById('syncStatusText');
                const syncControl = document.getElementById('syncControl');

                if (pauseButton) pauseButton.style.display = 'flex';
                if (resumeButton) resumeButton.style.display = 'none';
                if (statusText) statusText.textContent = 'Synced';
                if (syncControl) syncControl.className = 'sync-control-compact synced';

                // Small delay to allow backend state to propagate before checking status
                await new Promise(resolve => setTimeout(resolve, 200));
            } else {
                this.showToast('Failed to resume sync: ' + result.message, 'error');
            }
            await this.checkSyncStatus();
        } catch (error) {
            console.error('Error resuming sync:', error);
            this.showToast('Failed to resume sync', 'error');
        }
    }

    /**
     * Check backup status and update Settings modal
     */
    async checkBackupStatus() {
        try {
            const backupStatus = await this.apiCall('/backup/status');

            // Update backup elements in Settings modal
            const lastBackup = document.getElementById('settingsLastBackup');
            const backupCount = document.getElementById('settingsBackupCount');
            const nextBackup = document.getElementById('settingsNextBackup');

            if (!backupStatus.enabled) {
                if (lastBackup) lastBackup.textContent = 'Backups disabled';
                if (backupCount) backupCount.textContent = '-';
                if (nextBackup) nextBackup.textContent = '-';
                return;
            }

            // Update last backup time
            if (lastBackup) {
                if (backupStatus.time_since_last_seconds) {
                    lastBackup.textContent = this.formatTimeDelta(Math.floor(backupStatus.time_since_last_seconds)) + ' ago';
                } else {
                    lastBackup.textContent = 'Never';
                }
            }

            // Update backup count with size
            if (backupCount) {
                const sizeMB = (backupStatus.total_size_bytes / 1024 / 1024).toFixed(1);
                backupCount.textContent = `${backupStatus.backup_count} (${sizeMB} MB)`;
            }

            // Update next scheduled backup
            if (nextBackup && backupStatus.next_backup_at) {
                const nextDate = new Date(backupStatus.next_backup_at);
                nextBackup.textContent = nextDate.toLocaleString();
            } else if (nextBackup) {
                nextBackup.textContent = backupStatus.scheduler_running ? 'Scheduled' : 'Not scheduled';
            }

        } catch (error) {
            console.error('Error checking backup status:', error);
        }
    }

    /**
     * Create a backup manually
     */
    async createBackup() {
        const backupButton = document.getElementById('backupNowButton');
        if (backupButton) backupButton.disabled = true;

        try {
            this.showToast('Creating backup...', 'info');
            const result = await this.apiCall('/backup/now', 'POST');

            if (result.success) {
                const sizeMB = (result.size_bytes / 1024 / 1024).toFixed(2);
                this.showToast(`Backup created: ${result.filename} (${sizeMB} MB)`, 'success');
            } else {
                this.showToast('Backup failed: ' + result.error, 'error');
            }

            await this.checkBackupStatus();

        } catch (error) {
            console.error('Error creating backup:', error);
            this.showToast('Failed to create backup', 'error');
        } finally {
            if (backupButton) backupButton.disabled = false;
        }
    }

    /**
     * Start periodic sync status monitoring
     */
    startSyncStatusMonitoring() {
        // Check sync status every 10 seconds
        setInterval(() => {
            this.checkSyncStatus();
        }, 10000);
    }

    /**
     * Manually force sync to Cloudflare
     */
    async forceSync() {
        const syncButton = document.getElementById('forceSyncButton');
        const originalText = syncButton.innerHTML;

        try {
            // Check if sync was paused before force sync
            const statusBefore = await this.apiCall('/sync/status');
            const wasPaused = statusBefore.is_paused;

            // Set flag to prevent periodic polling from overwriting UI during force sync
            this._isForceSyncing = true;

            // Disable button and show loading state (just egg timer, no text - widget shows "Syncing")
            syncButton.disabled = true;
            syncButton.innerHTML = '<span class="sync-button-icon">⏳</span>';

            // IMMEDIATELY update sync control widget to show syncing state
            const statusText = document.getElementById('syncStatusText');
            const syncProgress = document.getElementById('syncProgress');
            const syncControl = document.getElementById('syncControl');

            if (statusText) statusText.textContent = 'Syncing';
            if (syncProgress) syncProgress.textContent = statusBefore.operations_pending > 0 ? `${statusBefore.operations_pending} pending` : '';
            if (syncControl) syncControl.className = 'sync-control-compact syncing';

            // Show toast when sync starts
            this.showToast('Starting sync...', 'info');

            const result = await this.apiCall('/sync/force', 'POST');

            if (result.success) {
                this.showToast(`Synced ${result.operations_synced} operations in ${result.time_taken_seconds}s`, 'success');

                // Refresh dashboard data to show newly synced memories
                if (this.currentView === 'dashboard') {
                    await this.loadDashboardData();
                }

                // If sync was paused before, pause it again after force sync
                if (wasPaused) {
                    await this.apiCall('/sync/pause', 'POST');
                }
            } else {
                this.showToast('Sync failed: ' + result.message, 'error');
            }

        } catch (error) {
            console.error('Error forcing sync:', error);
            this.showToast('Failed to force sync: ' + error.message, 'error');
        } finally {
            // Clear flag to allow periodic polling to resume
            this._isForceSyncing = false;

            // Re-enable button
            syncButton.disabled = false;
            syncButton.innerHTML = originalText;

            // Refresh sync status immediately
            await this.checkSyncStatus();
        }
    }

    /**
     * Render tags cloud from API data
     */
    renderTagsCloud() {
        const container = document.getElementById('tagsCloudContainer');
        const taggedContainer = document.getElementById('taggedMemoriesContainer');

        // Hide the tagged memories view initially
        taggedContainer.style.display = 'none';

        if (!this.tags || this.tags.length === 0) {
            container.innerHTML = '<p class="text-neutral-600">No tags found. Start adding tags to your memories to see them here.</p>';
            return;
        }

        // Render tag bubbles (tags are already sorted by count from backend)
        container.innerHTML = this.tags.map(tagData => `
            <button class="tag-bubble" data-tag="${this.escapeHtml(tagData.tag)}">
                ${this.escapeHtml(tagData.tag)}
                <span class="count">${tagData.count}</span>
            </button>
        `).join('');
    }

    /**
     * Filter memories by selected tag
     */
    async filterByTag(tag) {
        const taggedContainer = document.getElementById('taggedMemoriesContainer');
        const tagNameSpan = document.getElementById('selectedTagName');
        const memoriesList = document.getElementById('taggedMemoriesList');

        try {
            // Fetch memories for this specific tag
            const memoriesResponse = await this.apiCall(`/memories?tag=${encodeURIComponent(tag)}&limit=100`);
            const filteredMemories = memoriesResponse.memories || [];

            // Show the tagged memories section
            tagNameSpan.textContent = tag;
            taggedContainer.style.display = 'block';

            // Smooth scroll to results section for better UX
            taggedContainer.scrollIntoView({
                behavior: 'smooth',
                block: 'start'
            });

            // Render filtered memories
            this.renderMemoriesInContainer(filteredMemories, memoriesList);

            // Add event listener for clear filter button
            const clearBtn = document.getElementById('clearTagFilter');
            clearBtn.onclick = () => this.clearTagFilter();
        } catch (error) {
            console.error('Error filtering by tag:', error);
            this.showToast('Failed to load memories for tag', 'error');
        }
    }

    /**
     * Clear tag filter and show all tags
     */
    clearTagFilter() {
        const taggedContainer = document.getElementById('taggedMemoriesContainer');
        taggedContainer.style.display = 'none';
    }

    /**
     * Escape HTML to prevent XSS
     */
    escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    /**
     * Toggle chunking help section visibility
     */
    toggleChunkingHelp() {
        const helpSection = document.getElementById('chunkingHelpSection');
        if (helpSection) {
            if (helpSection.style.display === 'none') {
                helpSection.style.display = 'block';
            } else {
                helpSection.style.display = 'none';
            }
        }
    }

    /**
     * Hide chunking help section
     */
    hideChunkingHelp() {
        const helpSection = document.getElementById('chunkingHelpSection');
        if (helpSection) {
            helpSection.style.display = 'none';
        }
    }

    /**
     * Toggle overlap help section visibility
     */
    toggleOverlapHelp() {
        const helpSection = document.getElementById('overlapHelpSection');
        if (helpSection) {
            if (helpSection.style.display === 'none') {
                helpSection.style.display = 'block';
            } else {
                helpSection.style.display = 'none';
            }
        }
    }

    /**
     * Hide overlap help section
     */
    hideOverlapHelp() {
        const helpSection = document.getElementById('overlapHelpSection');
        if (helpSection) {
            helpSection.style.display = 'none';
        }
    }

    /**
     * Toggle processing mode help section visibility
     */
    toggleProcessingModeHelp() {
        const helpSection = document.getElementById('processingModeHelpSection');
        if (helpSection) {
            helpSection.style.display = helpSection.style.display === 'block' ? 'none' : 'block';
        }
    }

    /**
     * Hide processing mode help section
     */
    hideProcessingModeHelp() {
        const helpSection = document.getElementById('processingModeHelpSection');
        if (helpSection) {
            helpSection.style.display = 'none';
        }
    }

    /**
     * Render memories in a specific container
     */
    renderMemoriesInContainer(memories, container) {
        if (!memories || memories.length === 0) {
            container.innerHTML = '<p class="empty-state">No memories found with this tag.</p>';
            return;
        }

        container.innerHTML = memories.map(memory => this.renderMemoryCard(memory)).join('');

        // Add click handlers
        container.querySelectorAll('.memory-card').forEach((card, index) => {
            card.addEventListener('click', () => this.handleMemoryClick(memories[index]));
        });
    }

    /**
     * Handle navigation between views
     */
    handleNavigation(e) {
        const viewName = e.currentTarget.dataset.view;
        this.switchView(viewName);
    }

    /**
     * Switch between different views
     */
    switchView(viewName) {
        // Update navigation active state (if navigation exists)
        document.querySelectorAll('.nav-item').forEach(item => {
            item.classList.remove('active');
        });
        const navItem = document.querySelector(`[data-view="${viewName}"]`);
        if (navItem) {
            navItem.classList.add('active');
        }

        // Hide all views (if view containers exist)
        document.querySelectorAll('.view-container').forEach(view => {
            view.classList.remove('active');
        });

        // Show target view (if it exists)
        const targetView = document.getElementById(`${viewName}View`);
        if (targetView) {
            targetView.classList.add('active');
            this.currentView = viewName;

            // Load view-specific data
            this.loadViewData(viewName);
        }
    }

    /**
     * Load data specific to the current view
     */
    async loadViewData(viewName) {
        switch (viewName) {
            case 'search':
                // Initialize search view with recent search or empty state
                break;
            case 'browse':
                await this.loadBrowseData();
                break;
            case 'documents':
                await this.loadDocumentsData();
                break;
            case 'manage':
                await this.loadManageData();
                break;
            case 'analytics':
                await this.loadAnalyticsData();
                break;
            case 'apiDocs':
                // API docs view - static content, no additional loading needed
                break;
            default:
                // Dashboard view is loaded in loadDashboardData
                break;
        }
    }

    /**
     * Handle quick search input
     */
    async handleQuickSearch(e) {
        const query = e.target.value.trim();
        if (query.length >= 2) {
            try {
                const results = await this.searchMemories(query);
                // Could show dropdown suggestions here
            } catch (error) {
                console.error('Quick search error:', error);
            }
        }
    }

    /**
     * Handle full search
     */
    async handleSearch(query) {
        if (!query.trim()) return;

        this.switchView('search');
        this.setLoading(true);

        try {
            const results = await this.searchMemories(query);
            this.searchResults = results;
            this.renderSearchResults(results);
            this.updateResultsCount(results.length);
        } catch (error) {
            console.error('Search error:', error);
            this.showToast('Search failed', 'error');
        } finally {
            this.setLoading(false);
        }
    }

    /**
     * Search memories using the API
     */
    async searchMemories(query, filters = {}) {
        // Detect tag search patterns: #tag, tag:value, or "tag:value"
        const tagPattern = /^(#|tag:)(.+)$/i;
        const tagMatch = query.match(tagPattern);

        if (tagMatch) {
            // Use tag search endpoint
            const tagValue = tagMatch[2].trim();
            const payload = {
                tags: [tagValue],
                match_all: false // ANY match by default
            };

            const response = await this.apiCall('/search/by-tag', 'POST', payload);
            return response.results || [];
        } else {
            // Use semantic search endpoint
            const payload = {
                query: query,
                n_results: filters.limit || 20,
                ...filters
            };

            // Only add similarity_threshold if explicitly set in filters
            if (filters.threshold !== undefined) {
                payload.similarity_threshold = filters.threshold;
            }

            const response = await this.apiCall('/search', 'POST', payload);
            return response.results || [];
        }
    }

    /**
     * Handle filter changes in search view
     */
    async handleFilterChange() {
        const tagFilter = document.getElementById('tagFilter')?.value;
        const dateFilter = document.getElementById('dateFilter')?.value;
        const typeFilter = document.getElementById('typeFilter')?.value;
        const query = document.getElementById('quickSearch')?.value?.trim() || '';

        // Add loading state
        const applyBtn = document.getElementById('applyFiltersBtn');
        if (applyBtn) {
            applyBtn.classList.add('loading');
            applyBtn.disabled = true;
        }

        try {
            let results = [];

            // Priority 1: If we have a semantic query, start with semantic search
            if (query) {
                const filters = {};
                if (typeFilter) filters.type = typeFilter;
                results = await this.searchMemories(query, filters);

                // Apply tag filtering to semantic search results if tags are specified
                if (tagFilter && tagFilter.trim()) {
                    const tags = tagFilter.split(',').map(t => t.trim()).filter(t => t);
                    if (tags.length > 0) {
                        results = results.filter(result => {
                            const memoryTags = result.memory.tags || [];
                            // Check if any of the specified tags match memory tags (case-insensitive)
                            return tags.some(filterTag =>
                                memoryTags.some(memoryTag =>
                                    memoryTag.toLowerCase().includes(filterTag.toLowerCase())
                                )
                            );
                        });
                    }
                }
            }
            // Priority 2: Tag-only search (when no semantic query)
            else if (tagFilter && tagFilter.trim()) {
                const tags = tagFilter.split(',').map(t => t.trim()).filter(t => t);

                if (tags.length > 0) {
                    const payload = {
                        tags: tags,
                        match_all: false // ANY match by default
                    };

                    const response = await this.apiCall('/search/by-tag', 'POST', payload);
                    results = response.results || [];

                    // Apply type filter if present
                    if (typeFilter && typeFilter.trim()) {
                        results = results.filter(result => {
                            const memoryType = result.memory.memory_type || 'note';
                            return memoryType === typeFilter;
                        });
                    }
                }
            }
            // Priority 3: Date-based search
            else if (dateFilter && dateFilter.trim()) {
                const payload = {
                    query: dateFilter,
                    n_results: 100
                };
                const response = await this.apiCall('/search/by-time', 'POST', payload);
                results = response.results || [];

                // Apply type filter if present
                if (typeFilter && typeFilter.trim()) {
                    results = results.filter(result => {
                        const memoryType = result.memory.memory_type || 'note';
                        return memoryType === typeFilter;
                    });
                }
            }
            // Priority 4: Type-only filter
            else if (typeFilter && typeFilter.trim()) {
                const allMemoriesResponse = await this.apiCall('/memories?page=1&page_size=1000');
                if (allMemoriesResponse.memories) {
                    results = allMemoriesResponse.memories
                        .filter(memory => (memory.memory_type || 'note') === typeFilter)
                        .map(memory => ({ memory, similarity: 1.0 }));
                }
            } else {
                // No filters, clear results
                results = [];
            }

            this.searchResults = results;
            this.renderSearchResults(results);
            this.updateResultsCount(results.length);
            this.updateActiveFilters();

        } catch (error) {
            console.error('Filter search error:', error);
            this.showToast('Filter search failed', 'error');
        } finally {
            // Remove loading state
            const applyBtn = document.getElementById('applyFiltersBtn');
            if (applyBtn) {
                applyBtn.classList.remove('loading');
                applyBtn.disabled = false;
            }
        }
    }

    /**
     * Handle view mode changes (grid/list)
     */
    handleViewModeChange(mode) {
        document.querySelectorAll('.view-btn').forEach(btn => {
            btn.classList.remove('active');
        });
        document.querySelector(`[data-view="${mode}"]`).classList.add('active');

        const resultsContainer = document.getElementById('searchResultsList');
        resultsContainer.className = mode === 'grid' ? 'memory-grid' : 'memory-list';
    }

    /**
     * Handle quick actions
     */
    handleQuickAction(action) {
        switch (action) {
            case 'quick-search':
                this.switchView('search');
                const searchInput = document.getElementById('quickSearch');
                if (searchInput) {
                    searchInput.focus();
                }
                break;
            case 'add-memory':
                this.handleAddMemory();
                break;
            case 'browse-tags':
                this.switchView('browse');
                break;
            case 'export-data':
                this.handleExportData();
                break;
        }
    }

    /**
     * Handle add memory action
     */
    handleAddMemory() {
        const modal = document.getElementById('addMemoryModal');

        // Reset modal for adding new memory
        this.resetAddMemoryModal();

        this.openModal(modal);
        document.getElementById('memoryContent').focus();
    }

    /**
     * Reset add memory modal to default state
     */
    resetAddMemoryModal() {
        const modal = document.getElementById('addMemoryModal');
        const title = modal.querySelector('.modal-header h3');
        const saveBtn = document.getElementById('saveMemoryBtn');

        // Reset modal title and button text
        title.textContent = 'Add New Memory';
        saveBtn.textContent = 'Save Memory';

        // Clear form
        document.getElementById('addMemoryForm').reset();

        // Clear editing state
        this.editingMemory = null;
    }

    /**
     * Handle save memory
     */
    async handleSaveMemory() {
        const content = document.getElementById('memoryContent').value.trim();
        const tags = document.getElementById('memoryTags').value.trim();
        const type = document.getElementById('memoryType').value;

        if (!content) {
            this.showToast('Please enter memory content', 'warning');
            return;
        }

        const payload = {
            content: content,
            tags: tags ? tags.split(',').map(t => t.trim()) : [],
            memory_type: type,
            metadata: {
                created_via: 'dashboard',
                user_agent: navigator.userAgent,
                updated_via: this.editingMemory ? 'dashboard_edit' : 'dashboard_create'
            }
        };


        try {
            let response;
            let successMessage;

            if (this.editingMemory) {
                // Smart update: check if only metadata changed vs content changes
                const originalContentHash = this.editingMemory.content_hash;
                const contentChanged = this.editingMemory.content !== payload.content;


                if (!contentChanged) {
                    // Only metadata (tags, type, metadata) changed - use PUT endpoint
                    const updatePayload = {
                        tags: payload.tags,
                        memory_type: payload.memory_type,
                        metadata: payload.metadata
                    };

                    response = await this.apiCall(`/memories/${originalContentHash}`, 'PUT', updatePayload);
                    successMessage = 'Memory updated successfully';
                } else {
                    // Content changed - use create-delete approach (but with proper error handling)

                    try {
                        // Step 1: Create updated memory first
                        response = await this.apiCall('/memories', 'POST', payload);

                        // CRITICAL: Only proceed with deletion if creation actually succeeded
                        if (response.success) {
                            successMessage = 'Memory updated successfully';

                            try {
                                // Step 2: Delete original memory (only after successful creation)
                                const deleteResponse = await this.apiCall(`/memories/${originalContentHash}`, 'DELETE');
                            } catch (deleteError) {
                                console.error('Failed to delete original memory after creating new version:', deleteError);
                                this.showToast('Memory updated, but original version still exists. You may need to manually delete the duplicate.', 'warning');
                            }
                        } else {
                            // Creation failed - do NOT delete original memory
                            console.error('Creation failed:', response.message);
                            throw new Error(`Failed to create updated memory: ${response.message}`);
                        }
                    } catch (createError) {
                        // CREATE failed - original memory intact, no cleanup needed
                        console.error('Failed to create updated memory:', createError);
                        throw new Error(`Failed to update memory: ${createError.message}`);
                    }
                }
            } else {
                // Create new memory
                response = await this.apiCall('/memories', 'POST', payload);
                successMessage = 'Memory saved successfully';
            }

            this.closeModal(document.getElementById('addMemoryModal'));
            this.showToast(successMessage, 'success');

            // Reset editing state
            this.editingMemory = null;
            this.resetAddMemoryModal();

            // Refresh current view if needed
            if (this.currentView === 'dashboard') {
                this.loadDashboardData();
            } else if (this.currentView === 'search') {
                // Refresh search results
                const query = document.getElementById('searchInput').value.trim();
                if (query) {
                    this.handleSearch(query);
                }
            } else if (this.currentView === 'browse') {
                // Refresh browse view (tags cloud)
                this.loadBrowseData();
            }
        } catch (error) {
            console.error('Error saving memory:', error);
            this.showToast(error.message || 'Failed to save memory', 'error');
        }
    }

    /**
     * Handle memory click to show details
     */
    handleMemoryClick(memory) {
        this.showMemoryDetails(memory);
    }

    /**
     * Show memory details in modal
     */
    showMemoryDetails(memory) {
        const modal = document.getElementById('memoryModal');
        const title = document.getElementById('modalTitle');
        const content = document.getElementById('modalContent');

        title.textContent = 'Memory Details';
        content.innerHTML = this.renderMemoryDetails(memory);

        // Set up action buttons
        document.getElementById('editMemoryBtn').onclick = () => this.editMemory(memory);
        document.getElementById('deleteMemoryBtn').onclick = () => this.deleteMemory(memory);
        document.getElementById('shareMemoryBtn').onclick = () => this.shareMemory(memory);

        this.openModal(modal);
    }

    /**
     * Render memory details HTML
     */
    renderMemoryDetails(memory) {
        const createdDate = new Date(memory.created_at * 1000).toLocaleString();
        const updatedDate = memory.updated_at ? new Date(memory.updated_at * 1000).toLocaleString() : null;

        return `
            <div class="memory-detail">
                <div class="memory-meta">
                    <p><strong>Created:</strong> ${createdDate}</p>
                    ${updatedDate ? `<p><strong>Updated:</strong> ${updatedDate}</p>` : ''}
                    <p><strong>Type:</strong> ${memory.memory_type || 'note'}</p>
                    <p><strong>ID:</strong> ${memory.content_hash}</p>
                </div>

                <div class="memory-content">
                    <h4>Content</h4>
                    <div class="content-text">${this.escapeHtml(memory.content)}</div>
                </div>

                ${memory.tags && memory.tags.length > 0 ? `
                    <div class="memory-tags-section">
                        <h4>Tags</h4>
                        <div class="memory-tags">
                            ${memory.tags.map(tag => `<span class="tag">${this.escapeHtml(tag)}</span>`).join('')}
                        </div>
                    </div>
                ` : ''}

                ${memory.metadata ? `
                    <div class="memory-metadata">
                        <h4 class="metadata-toggle" onclick="this.parentElement.classList.toggle('expanded')" style="cursor: pointer; user-select: none;">
                            <span class="toggle-icon">▶</span> Metadata
                        </h4>
                        <div class="metadata-content">
                            ${this.renderMetadata(memory.metadata)}
                        </div>
                    </div>
                ` : ''}
            </div>
        `;
    }

    /**
     * Render metadata in a prettier format
     */
    renderMetadata(metadata) {
        if (!metadata || typeof metadata !== 'object') {
            return '<p class="metadata-empty">No metadata available</p>';
        }

        let html = '<div class="metadata-items">';

        for (const [key, value] of Object.entries(metadata)) {
            let displayValue;

            if (typeof value === 'string') {
                displayValue = `<span class="metadata-string">"${this.escapeHtml(value)}"</span>`;
            } else if (typeof value === 'number') {
                displayValue = `<span class="metadata-number">${value}</span>`;
            } else if (typeof value === 'boolean') {
                displayValue = `<span class="metadata-boolean">${value}</span>`;
            } else if (Array.isArray(value)) {
                displayValue = `<span class="metadata-array">[${value.map(v =>
                    typeof v === 'string' ? `"${this.escapeHtml(v)}"` : v
                ).join(', ')}]</span>`;
            } else {
                displayValue = `<span class="metadata-object">${JSON.stringify(value)}</span>`;
            }

            html += `
                <div class="metadata-item">
                    <span class="metadata-key">${this.escapeHtml(key)}:</span>
                    <span class="metadata-value">${displayValue}</span>
                </div>
            `;
        }

        html += '</div>';
        return html;
    }

    /**
     * Delete memory
     */
    async deleteMemory(memory) {
        if (!confirm('Are you sure you want to delete this memory? This action cannot be undone.')) {
            return;
        }

        try {
            await this.apiCall(`/memories/${memory.content_hash}`, 'DELETE');
            this.closeModal(document.getElementById('memoryModal'));
            this.showToast('Memory deleted successfully', 'success');

            // Refresh current view
            if (this.currentView === 'dashboard') {
                this.loadDashboardData();
            } else if (this.currentView === 'search') {
                this.searchResults = this.searchResults.filter(m => m.memory.content_hash !== memory.content_hash);
                this.renderSearchResults(this.searchResults);
            } else if (this.currentView === 'browse') {
                // Refresh browse view (tags cloud)
                this.loadBrowseData();
            }
        } catch (error) {
            console.error('Error deleting memory:', error);
            this.showToast('Failed to delete memory', 'error');
        }
    }

    /**
     * Edit memory
     */
    editMemory(memory) {
        // Close the memory details modal first
        this.closeModal(document.getElementById('memoryModal'));

        // Open the add memory modal with pre-filled data
        const modal = document.getElementById('addMemoryModal');
        const title = modal.querySelector('.modal-header h3');
        const saveBtn = document.getElementById('saveMemoryBtn');

        // Update modal for editing
        title.textContent = 'Edit Memory';
        saveBtn.textContent = 'Update Memory';

        // Pre-fill the form with existing data
        document.getElementById('memoryContent').value = memory.content || '';

        // Handle tags - ensure they're displayed correctly
        const tagsValue = memory.tags && Array.isArray(memory.tags) ? memory.tags.join(', ') : '';
        document.getElementById('memoryTags').value = tagsValue;

        document.getElementById('memoryType').value = memory.memory_type || 'note';


        // Store the memory being edited
        this.editingMemory = memory;

        this.openModal(modal);

        // Use setTimeout to ensure modal is fully rendered before setting values
        setTimeout(() => {
            document.getElementById('memoryContent').focus();
        }, 100);
    }

    /**
     * Share memory
     */
    shareMemory(memory) {
        // Create shareable data
        const shareData = {
            content: memory.content,
            tags: memory.tags || [],
            type: memory.memory_type || 'note',
            created: new Date(memory.created_at * 1000).toISOString(),
            id: memory.content_hash
        };

        // Try to use Web Share API if available
        if (navigator.share) {
            navigator.share({
                title: 'Memory from MCP Memory Service',
                text: memory.content,
                url: window.location.href
            }).catch(err => {
                // Share API failed, fall back to clipboard
                this.fallbackShare(shareData);
            });
        } else {
            this.fallbackShare(shareData);
        }
    }

    /**
     * Fallback share method (copy to clipboard)
     */
    fallbackShare(shareData) {
        const shareText = `Memory Content:\n${shareData.content}\n\nTags: ${shareData.tags.join(', ')}\nType: ${shareData.type}\nCreated: ${shareData.created}`;

        navigator.clipboard.writeText(shareText).then(() => {
            this.showToast('Memory copied to clipboard', 'success');
        }).catch(err => {
            console.error('Could not copy text: ', err);
            this.showToast('Failed to copy to clipboard', 'error');
        });
    }

    /**
     * Handle data export
     */
    async handleExportData() {
        try {
            this.showToast('Preparing export...', 'info');

            // Fetch all memories using pagination
            const allMemories = [];
            const pageSize = 100; // Reasonable batch size
            let page = 1;
            let hasMore = true;
            let totalMemories = 0;

            while (hasMore) {
                const response = await this.apiCall(`/memories?page=${page}&page_size=${pageSize}`);

                if (page === 1) {
                    totalMemories = response.total;
                }

                if (response.memories && response.memories.length > 0) {
                    allMemories.push(...response.memories);
                    hasMore = response.has_more;
                    page++;

                    // Update progress
                    this.showToast(`Fetching memories... (${allMemories.length}/${totalMemories})`, 'info');
                } else {
                    hasMore = false;
                }
            }

            const data = {
                export_date: new Date().toISOString(),
                total_memories: totalMemories,
                exported_memories: allMemories.length,
                memories: allMemories
            };

            const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `mcp-memories-export-${new Date().toISOString().split('T')[0]}.json`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);

            this.showToast(`Successfully exported ${allMemories.length} memories`, 'success');
        } catch (error) {
            console.error('Export error:', error);
            this.showToast('Failed to export data', 'error');
        }
    }

    /**
    * Render recent memories
    */
    renderRecentMemories(memories) {
    const container = document.getElementById('recentMemoriesList');

    if (!container) {
    console.error('recentMemoriesList container not found');
    return;
    }

    if (!memories || memories.length === 0) {
    container.innerHTML = '<p class="empty-state">No memories found. <a href="#" onclick="app.handleAddMemory()">Add your first memory</a></p>';
    return;
    }

    // Group document chunks by upload_id
        const groupedMemories = this.groupMemoriesByUpload(memories);

    container.innerHTML = groupedMemories.map(group => {
    if (group.type === 'document') {
            return this.renderDocumentGroup(group);
            } else {
                return this.renderMemoryCard(group.memory);
            }
        }).join('');

        // Add click handlers for individual memories
        container.querySelectorAll('.memory-card').forEach((card, index) => {
            const group = groupedMemories[index];
            if (group.type === 'single') {
                card.addEventListener('click', () => this.handleMemoryClick(group.memory));
            }
        });

        // Add click handlers for document groups
        container.querySelectorAll('.document-group').forEach((groupEl, index) => {
            const group = groupedMemories.filter(g => g.type === 'document')[index];
            if (group) {
                groupEl.addEventListener('click', (e) => {
                    // Don't trigger if clicking on action buttons
                    if (e.target.closest('.document-actions')) return;
                    this.showDocumentChunks(group);
                });
            }
        });

        // Add click handlers for document action buttons
        container.querySelectorAll('.document-group').forEach((groupEl, index) => {
            const group = groupedMemories.filter(g => g.type === 'document')[index];
            if (group) {
                // View chunks button
                const viewBtn = groupEl.querySelector('.btn-view-chunks');
                if (viewBtn) {
                    viewBtn.addEventListener('click', (e) => {
                        e.stopPropagation();
                        this.showDocumentChunks(group);
                    });
                }

                // Remove button
                const removeBtn = groupEl.querySelector('.btn-remove');
                if (removeBtn) {
                    removeBtn.addEventListener('click', async (e) => {
                        e.stopPropagation();
                        await this.removeDocument(group.upload_id, group.source_file);
                        // removeDocument() already handles view refresh
                    });
                }
            }
        });
    }

    /**
     * Group memories by upload_id for document chunks
     */
    groupMemoriesByUpload(memories) {
        const groups = [];
        const documentGroups = new Map();
        const processedHashes = new Set();

        for (const memory of memories) {
            // Check if this is a document chunk
            const isDocumentChunk = memory.metadata && memory.metadata.upload_id;

            if (isDocumentChunk && !processedHashes.has(memory.content_hash)) {
                const uploadId = memory.metadata.upload_id;
                const sourceFile = memory.metadata.source_file || 'Unknown file';

                if (!documentGroups.has(uploadId)) {
                    documentGroups.set(uploadId, {
                        upload_id: uploadId,
                        source_file: sourceFile,
                        memories: [],
                        created_at: memory.created_at,
                        tags: new Set()
                    });
                }

                const group = documentGroups.get(uploadId);
                group.memories.push(memory);
                group.tags.add(...(memory.tags || []));
                processedHashes.add(memory.content_hash);
            } else if (!processedHashes.has(memory.content_hash)) {
                // Regular memory
                groups.push({
                    type: 'single',
                    memory: memory
                });
                processedHashes.add(memory.content_hash);
            }
        }

        // Convert document groups to array format
        for (const group of documentGroups.values()) {
            groups.push({
                type: 'document',
                upload_id: group.upload_id,
                source_file: group.source_file,
                memories: group.memories,
                created_at: group.created_at,
                tags: Array.from(group.tags)
            });
        }

        // Sort by creation time (most recent first)
        groups.sort((a, b) => {
            const timeA = a.type === 'document' ? a.created_at : a.memory.created_at;
            const timeB = b.type === 'document' ? b.created_at : b.memory.created_at;
            return timeB - timeA;
        });

        return groups;
    }

    /**
     * Render a document group card
     */
    renderDocumentGroup(group) {
        const createdDate = new Date(group.created_at * 1000).toLocaleDateString();
        const fileName = this.escapeHtml(group.source_file);
        const chunkCount = group.memories.length;
        // Filter out metadata tags AND tags that are too long (likely corrupted/malformed)
        const uniqueTags = [...new Set(group.tags.filter(tag =>
            !tag.startsWith('upload_id:') &&
            !tag.startsWith('source_file:') &&
            !tag.startsWith('file_type:') &&
            tag.length < 100  // Reject tags longer than 100 chars (likely corrupted metadata)
        ))];

        return `
            <div class="document-group" data-upload-id="${this.escapeHtml(group.upload_id)}">
                <div class="document-header">
                    <div class="document-icon">📄</div>
                    <div class="document-info">
                        <div class="document-title">${fileName}</div>
                        <div class="document-meta">
                            ${chunkCount} chunks • ${createdDate}
                        </div>
                    </div>
                </div>
                <div class="document-preview">
                    ${group.memories[0] ? this.escapeHtml(group.memories[0].content.substring(0, 150)) + (group.memories[0].content.length > 150 ? '...' : '') : 'No content preview available'}
                </div>
                ${uniqueTags.length > 0 ? `
                    <div class="document-tags">
                        ${uniqueTags.slice(0, 3).map(tag => `<span class="tag">${this.escapeHtml(tag)}</span>`).join('')}
                        ${uniqueTags.length > 3 ? `<span class="tag more">+${uniqueTags.length - 3} more</span>` : ''}
                    </div>
                ` : ''}
                <div class="document-actions">
                    <button class="btn-icon btn-view-chunks" title="View all chunks">
                        <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
                            <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
                        </svg>
                        <span>View Chunks</span>
                    </button>
                    <button class="btn-icon btn-remove" title="Remove document">
                        <svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
                            <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
                        </svg>
                        <span>Remove</span>
                    </button>
                </div>
            </div>
        `;
    }

    /**
     * Show document chunks in a modal
     */
    showDocumentChunks(group) {
        const modal = document.createElement('div');
        modal.className = 'modal-overlay';
        modal.innerHTML = `
            <div class="modal-content large-modal">
                <div class="modal-header">
                    <h3>📄 ${this.escapeHtml(group.source_file)}</h3>
                    <button class="modal-close">&times;</button>
                </div>
                <div class="modal-body">
                    <div class="document-chunks">
                        ${group.memories.map((memory, index) => `
                            <div class="chunk-item">
                                <div class="chunk-header">
                                    <span class="chunk-number">Chunk ${index + 1}</span>
                                    <div class="chunk-meta">
                                        ${memory.metadata && memory.metadata.page ? `Page ${memory.metadata.page} • ` : ''}
                                        ${memory.content.length} chars
                                    </div>
                                </div>
                                <div class="chunk-content">
                                    ${this.escapeHtml(memory.content)}
                                </div>
                                ${memory.tags && memory.tags.length > 0 ? `
                                    <div class="chunk-tags">
                                        ${memory.tags.map(tag => `<span class="tag">${this.escapeHtml(tag)}</span>`).join('')}
                                    </div>
                                ` : ''}
                            </div>
                        `).join('')}
                    </div>
                </div>
            </div>
        `;

        document.body.appendChild(modal);

        // Add active class to show modal (required for display: flex)
        setTimeout(() => modal.classList.add('active'), 10);

        // Add close handlers
        const closeModal = () => {
            modal.classList.remove('active');
            setTimeout(() => document.body.removeChild(modal), 300);
        };
        modal.querySelector('.modal-close').addEventListener('click', closeModal);
        modal.addEventListener('click', (e) => {
            if (e.target === modal) closeModal();
        });
    }

    /**
     * Render tags in sidebar
     */
    renderTagsSidebar(tags) {
        const container = document.getElementById('tagsCloudContainer');

        if (!container) {
            console.warn('tagsCloudContainer element not found - skipping tags sidebar rendering');
            return;
        }

        if (!tags || tags.length === 0) {
            container.innerHTML = '<div class="no-tags">No tags found.</div>';
            return;
        }

        // Take top tags for sidebar display
        const topTags = tags.slice(0, 10);
        container.innerHTML = topTags.map(tagData => `
            <div class="tag-item" data-tag="${this.escapeHtml(tagData.tag)}">
                <span class="tag-name">${this.escapeHtml(tagData.tag)}</span>
                <span class="tag-count">${tagData.count}</span>
            </div>
        `).join('');

        // Add click handlers
        container.querySelectorAll('.tag-item').forEach(item => {
            item.addEventListener('click', () => {
                const tagName = item.dataset.tag;
                const searchInput = document.getElementById('searchInput');
                searchInput.value = `#${tagName}`;
                this.handleSearch(`#${tagName}`);
            });
        });
    }

    /**
     * Render search results
     */
    renderSearchResults(results) {
        const container = document.getElementById('searchResultsList');

        if (!results || results.length === 0) {
            container.innerHTML = '<p class="empty-state">No results found. Try a different search term.</p>';
            return;
        }

        container.innerHTML = results.map(result => this.renderMemoryCard(result.memory, result)).join('');

        // Add click handlers
        container.querySelectorAll('.memory-card').forEach((card, index) => {
            card.addEventListener('click', () => this.handleMemoryClick(results[index].memory));
        });
    }

    /**
    * Render a memory card
    */
    renderMemoryCard(memory, searchResult = null) {
    const createdDate = new Date(memory.created_at * 1000).toLocaleDateString();
    const relevanceScore = searchResult &&
    searchResult.similarity_score !== null &&
    searchResult.similarity_score !== undefined &&
    !isNaN(searchResult.similarity_score) &&
    searchResult.similarity_score > 0
    ? (searchResult.similarity_score * 100).toFixed(1)
    : null;

    // Truncate content to 150 characters for preview
    const truncatedContent = memory.content.length > 150
    ? memory.content.substring(0, 150) + '...'
    : memory.content;

    return `
    <div class="memory-card" data-memory-id="${memory.content_hash}">
    <div class="memory-header">
        <div class="memory-meta">
                        <span>${createdDate}</span>
            ${memory.memory_type ? `<span> • ${memory.memory_type}</span>` : ''}
        ${relevanceScore ? `<span> • ${relevanceScore}% match</span>` : ''}
        </div>
                </div>

    <div class="memory-content">
    ${this.escapeHtml(truncatedContent)}
    </div>

        ${memory.tags && memory.tags.length > 0 ? `
                <div class="memory-tags">
                        ${memory.tags.map(tag => `<span class="tag">${this.escapeHtml(tag)}</span>`).join('')}
                    </div>
                ` : ''}
            </div>
        `;
    }

    /**
     * Update dashboard statistics
     */
    updateDashboardStats(stats) {
        const totalMemoriesEl = document.getElementById('totalMemories');
        if (totalMemoriesEl) {
            totalMemoriesEl.textContent = stats.total_memories || '0';
        }

        const recentMemoriesEl = document.getElementById('recentMemories');
        if (recentMemoriesEl) {
            recentMemoriesEl.textContent = stats.memories_this_week || '0';
        }

        const uniqueTagsEl = document.getElementById('uniqueTags');
        if (uniqueTagsEl) {
            uniqueTagsEl.textContent = stats.unique_tags || '0';
        }

        const storageBackendEl = document.getElementById('storageBackend');
        if (storageBackendEl) {
            storageBackendEl.textContent = stats.backend || 'unknown';
        }
    }

    /**
     * Update search results count
     */
    updateResultsCount(count) {
        const element = document.getElementById('resultsCount');
        if (element) {
            element.textContent = `${count} result${count !== 1 ? 's' : ''}`;
        }
    }

    /**
     * Update active filters display
     */
    updateActiveFilters() {
        const activeFiltersContainer = document.getElementById('activeFilters');
        const filtersList = document.getElementById('activeFiltersList');

        if (!activeFiltersContainer || !filtersList) return;

        const tagFilter = document.getElementById('tagFilter')?.value?.trim();
        const dateFilter = document.getElementById('dateFilter')?.value;
        const typeFilter = document.getElementById('typeFilter')?.value;

        const filters = [];

        if (tagFilter) {
            const tags = tagFilter.split(',').map(t => t.trim()).filter(t => t);
            tags.forEach(tag => {
                filters.push({
                    type: 'tag',
                    value: tag,
                    label: `Tag: ${tag}`
                });
            });
        }

        if (dateFilter) {
            const dateLabels = {
                'today': 'Today',
                'week': 'This week',
                'month': 'This month',
                'year': 'This year'
            };
            filters.push({
                type: 'date',
                value: dateFilter,
                label: `Date: ${dateLabels[dateFilter] || dateFilter}`
            });
        }

        if (typeFilter) {
            const typeLabels = {
                'note': 'Notes',
                'code': 'Code',
                'reference': 'References',
                'idea': 'Ideas'
            };
            filters.push({
                type: 'type',
                value: typeFilter,
                label: `Type: ${typeLabels[typeFilter] || typeFilter}`
            });
        }

        if (filters.length === 0) {
            activeFiltersContainer.style.display = 'none';
            return;
        }

        activeFiltersContainer.style.display = 'block';
        filtersList.innerHTML = filters.map(filter => `
            <div class="filter-pill">
                ${this.escapeHtml(filter.label)}
                <button class="remove-filter" data-filter-type="${this.escapeHtml(filter.type)}" data-filter-value="${this.escapeHtml(filter.value)}">
                    ×
                </button>
            </div>
        `).join('');

        // Add event listeners for filter removal
        filtersList.addEventListener('click', (e) => {
            const button = e.target.closest('.remove-filter');
            if (!button) return;

            const type = button.dataset.filterType;
            const value = button.dataset.filterValue;
            this.removeFilter(type, value);
        });
    }

    /**
     * Remove a specific filter
     */
    removeFilter(type, value) {
        switch (type) {
            case 'tag':
                const tagInput = document.getElementById('tagFilter');
                if (tagInput) {
                    const tags = tagInput.value.split(',').map(t => t.trim()).filter(t => t && t !== value);
                    tagInput.value = tags.join(', ');
                }
                break;
            case 'date':
                const dateSelect = document.getElementById('dateFilter');
                if (dateSelect) {
                    dateSelect.value = '';
                }
                break;
            case 'type':
                const typeSelect = document.getElementById('typeFilter');
                if (typeSelect) {
                    typeSelect.value = '';
                }
                break;
        }
        this.handleFilterChange();
    }

    /**
     * Clear all filters
     */
    clearAllFilters() {
        const tagFilter = document.getElementById('tagFilter');
        const dateFilter = document.getElementById('dateFilter');
        const typeFilter = document.getElementById('typeFilter');

        if (tagFilter) tagFilter.value = '';
        if (dateFilter) dateFilter.value = '';
        if (typeFilter) typeFilter.value = '';

        this.searchResults = [];
        this.renderSearchResults([]);
        this.updateResultsCount(0);
        this.updateActiveFilters();

        this.showToast('All filters cleared', 'info');
    }

    /**
     * Handle live search toggle
     */
    handleLiveSearchToggle(event) {
        this.liveSearchEnabled = event.target.checked;
        const modeText = document.getElementById('searchModeText');
        if (modeText) {
            modeText.textContent = this.liveSearchEnabled ? 'Live Search' : 'Manual Search';
        }

        // Show a toast to indicate the mode change
        this.showToast(
            `Search mode: ${this.liveSearchEnabled ? 'Live (searches as you type)' : 'Manual (click Search button)'}`,
            'info'
        );
    }

    /**
     * Handle debounced filter changes for live search
     */
    handleDebouncedFilterChange() {
        // Clear any existing timer
        if (this.debounceTimer) {
            clearTimeout(this.debounceTimer);
        }

        // Only trigger search if live search is enabled
        if (this.liveSearchEnabled) {
            this.debounceTimer = setTimeout(() => {
                this.handleFilterChange();
            }, 300); // 300ms debounce
        }
    }

    /**
     * Handle memory added via SSE
     */
    handleMemoryAdded(memory) {
        if (this.currentView === 'dashboard') {
            this.loadDashboardData();
        }
    }

    /**
     * Handle memory deleted via SSE
     */
    handleMemoryDeleted(memoryId) {
        // Remove from current view
        const cards = document.querySelectorAll(`[data-memory-id="${memoryId}"]`);
        cards.forEach(card => card.remove());

        // Update search results if in search view
        if (this.currentView === 'search') {
            this.searchResults = this.searchResults.filter(r => r.memory.content_hash !== memoryId);
            this.updateResultsCount(this.searchResults.length);
        }
    }

    /**
     * Handle memory updated via SSE
     */
    handleMemoryUpdated(memory) {
        // Refresh relevant views
        if (this.currentView === 'dashboard') {
            this.loadDashboardData();
        }
    }

    /**
     * Update connection status indicator
     */
    updateConnectionStatus(status) {
        const statusElement = document.getElementById('connectionStatus');
        if (statusElement) {
            const indicator = statusElement.querySelector('.status-indicator');
            const text = statusElement.querySelector('.status-text');
            if (!indicator || !text) return;

            // Reset indicator classes
            indicator.className = 'status-indicator';

            switch (status) {
                case 'connected':
                    text.textContent = 'Connected';
                    // Connected uses default green color (no additional class needed)
                    break;
                case 'connecting':
                    text.textContent = 'Connecting...';
                    indicator.classList.add('connecting');
                    break;
                case 'disconnected':
                    text.textContent = 'Disconnected';
                    indicator.classList.add('disconnected');
                    break;
                default:
                    text.textContent = 'Unknown';
                    indicator.classList.add('disconnected');
            }
        }
    }

    /**
     * Generic API call wrapper
     */
    async apiCall(endpoint, method = 'GET', data = null) {
        const options = {
            method: method,
            headers: {
                'Content-Type': 'application/json',
            }
        };

        if (data) {
            options.body = JSON.stringify(data);
        }

        const response = await fetch(`${this.apiBase}${endpoint}`, options);

        if (!response.ok) {
            const errorData = await response.json().catch(() => ({}));
            throw new Error(errorData.detail || `HTTP ${response.status}`);
        }

        return await response.json();
    }

    /**
     * Modal management
     */
    openModal(modal) {
        modal.classList.add('active');
        document.body.style.overflow = 'hidden';

        // Focus first input
        const firstInput = modal.querySelector('input, textarea');
        if (firstInput) {
            firstInput.focus();
        }
    }

    closeModal(modal) {
        modal.classList.remove('active');
        document.body.style.overflow = '';
    }

    /**
     * Loading state management
     */
    setLoading(loading) {
        this.isLoading = loading;
        const indicator = document.getElementById('loadingOverlay');
        if (indicator) {
            if (loading) {
                indicator.classList.remove('hidden');
            } else {
                indicator.classList.add('hidden');
            }
        }
    }

    /**
     * View document memory chunks
     */
    async viewDocumentMemory(uploadId) {
        try {
            this.setLoading(true);
            const response = await this.apiCall(`/documents/search-content/${uploadId}`);

            if (response.status === 'success' || response.status === 'partial') {
                // Show modal
                const modal = document.getElementById('memoryViewerModal');
                const filename = document.getElementById('memoryViewerFilename');
                const stats = document.getElementById('memoryViewerStats');
                const chunksList = document.getElementById('memoryChunksList');

                filename.textContent = response.filename || 'Document';
                stats.textContent = `${response.total_found} chunk${response.total_found !== 1 ? 's' : ''} found`;

                // Render chunks
                if (response.memories && response.memories.length > 0) {
                    const chunksHtml = response.memories.map((memory, index) => {
                        const chunkIndex = memory.chunk_index !== undefined ? memory.chunk_index : index;
                        const page = memory.page ? ` • Page ${memory.page}` : '';
                        const contentPreview = memory.content.length > 300
                            ? memory.content.substring(0, 300) + '...'
                            : memory.content;

                        return `
                            <div class="memory-chunk-item">
                                <div class="chunk-header">
                                    <span class="chunk-number">Chunk ${chunkIndex + 1}${page}</span>
                                    <span class="chunk-hash" title="${memory.content_hash}">${memory.content_hash.substring(0, 12)}...</span>
                                </div>
                                <div class="chunk-content">${this.escapeHtml(contentPreview)}</div>
                                <div class="chunk-tags">
                                    ${memory.tags.map(tag => `<span class="tag">${this.escapeHtml(tag)}</span>`).join('')}
                                </div>
                            </div>
                        `;
                    }).join('');

                    chunksList.innerHTML = chunksHtml;
                } else {
                    chunksList.innerHTML = '<p class="text-muted">No memory chunks found for this document.</p>';
                }

                modal.style.display = 'flex';

                if (response.status === 'partial') {
                    this.showToast(`Found ${response.total_found} chunks (partial results)`, 'warning');
                }
            } else {
                this.showToast('Failed to load document memories', 'error');
            }
        } catch (error) {
            console.error('Error viewing document memory:', error);
            this.showToast('Error loading document memories', 'error');
        } finally {
            this.setLoading(false);
        }
    }

    /**
     * Close memory viewer modal
     */
    closeMemoryViewer() {
        const modal = document.getElementById('memoryViewerModal');
        if (modal) {
            modal.style.display = 'none';
        }
    }

    /**
     * Remove document and its memories
     */
    async removeDocument(uploadId, filename) {
        console.log('removeDocument called with:', { uploadId, filename, currentView: this.currentView });

        if (!confirm(`Remove "${filename}" and all its memory chunks?\n\nThis action cannot be undone.`)) {
            console.log('User cancelled removal');
            return;
        }

        try {
            this.setLoading(true);
            console.log('Making DELETE request to:', `/documents/remove/${uploadId}`);

            const response = await this.apiCall(`/documents/remove/${uploadId}`, 'DELETE');

            console.log('Delete response:', response);

            if (response.status === 'success') {
                this.showToast(`Removed "${filename}" (${response.memories_deleted} memories deleted)`, 'success');
                // Refresh the current view (Dashboard or Documents tab)
                console.log('Refreshing view:', this.currentView);
                if (this.currentView === 'dashboard') {
                    await this.loadDashboardData();
                } else if (this.currentView === 'documents') {
                    await this.loadUploadHistory();
                }
            } else {
                console.error('Removal failed with response:', response);
                this.showToast('Failed to remove document', 'error');
            }
        } catch (error) {
            console.error('Error removing document:', error);
            console.error('Error stack:', error.stack);
            this.showToast('Error removing document', 'error');
        } finally {
            this.setLoading(false);
        }
    }

    /**
     * Search document content
     */
    async searchDocumentContent(query) {
        try {
            this.setLoading(true);
            const resultsContainer = document.getElementById('docSearchResults');
            const resultsList = document.getElementById('docSearchResultsList');
            const resultsCount = document.getElementById('docSearchCount');

            // Use the regular search endpoint but filter for document memories
            // Higher n_results to ensure we get enough document results after filtering
            const response = await this.apiCall('/search', 'POST', {
                query: query,
                n_results: 100
            });

            if (response.results) {
                // Filter results to only show document-type memories
                const documentResults = response.results.filter(r =>
                    r.memory?.memory_type === 'document' || (r.memory?.tags && r.memory.tags.some(tag => tag.startsWith('upload_id:')))
                );

                // Limit display to top 20 most relevant document results
                const displayResults = documentResults.slice(0, 20);

                resultsCount.textContent = `${documentResults.length} result${documentResults.length !== 1 ? 's' : ''}${documentResults.length > 20 ? ' (showing top 20)' : ''}`;

                if (displayResults.length > 0) {
                    const resultsHtml = displayResults.map(result => {
                        const mem = result.memory;
                        const uploadIdTag = mem.tags?.find(tag => tag.startsWith('upload_id:'));
                        const sourceFile = mem.metadata?.source_file || 'Unknown file';
                        const contentPreview = mem.content.length > 200
                            ? mem.content.substring(0, 200) + '...'
                            : mem.content;

                        return `
                            <div class="search-result-item">
                                <div class="result-header">
                                    <strong>${this.escapeHtml(sourceFile)}</strong>
                                    <span class="similarity-score">${Math.round((result.similarity_score || 0) * 100)}% match</span>
                                </div>
                                <div class="result-content">${this.escapeHtml(contentPreview)}</div>
                                <div class="result-tags">
                                    ${(mem.tags || []).slice(0, 5).map(tag => `<span class="tag">${this.escapeHtml(tag)}</span>`).join('')}
                                </div>
                            </div>
                        `;
                    }).join('');

                    resultsList.innerHTML = resultsHtml;
                } else {
                    resultsList.innerHTML = '<p class="text-muted">No matching document content found. Try different search terms.</p>';
                }

                resultsContainer.style.display = 'block';
            } else {
                this.showToast('Search failed', 'error');
            }
        } catch (error) {
            console.error('Error searching documents:', error);
            this.showToast('Error performing search', 'error');
        } finally {
            this.setLoading(false);
        }
    }

    /**
     * Toast notification system
     */
    showToast(message, type = 'info', duration = 5000) {
        const container = document.getElementById('toastContainer');
        const toast = document.createElement('div');
        toast.className = `toast ${type}`;
        toast.textContent = message;

        container.appendChild(toast);

        // Auto-remove after duration
        setTimeout(() => {
            toast.remove();
        }, duration);

        // Click to remove
        toast.addEventListener('click', () => {
            toast.remove();
        });
    }

    /**
     * Utility: Debounce function
     */
    debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    /**
     * Load settings from localStorage
     */
    loadSettings() {
        try {
            const saved = localStorage.getItem('memoryDashboardSettings');
            if (saved) {
                this.settings = { ...this.settings, ...JSON.parse(saved) };
            }
        } catch (error) {
            console.warn('Failed to load settings:', error);
        }
    }

    /**
     * Save settings to localStorage
     */
    saveSettingsToStorage() {
        try {
            localStorage.setItem('memoryDashboardSettings', JSON.stringify(this.settings));
        } catch (error) {
            console.error('Failed to save settings:', error);
            this.showToast('Failed to save settings. Your preferences will not be persisted.', 'error');
        }
    }

    /**
     * Apply theme to the page
     */
    applyTheme(theme = this.settings.theme) {
        const isDark = theme === 'dark';
        document.body.classList.toggle('dark-mode', isDark);

        // Toggle icon visibility using CSS classes
        const sunIcon = document.getElementById('sunIcon');
        const moonIcon = document.getElementById('moonIcon');
        if (sunIcon && moonIcon) {
            sunIcon.classList.toggle('hidden', isDark);
            moonIcon.classList.toggle('hidden', !isDark);
        }
    }

    /**
     * Toggle between light and dark theme
     */
    toggleTheme() {
        const newTheme = this.settings.theme === 'dark' ? 'light' : 'dark';
        this.settings.theme = newTheme;
        this.applyTheme(newTheme);
        this.saveSettingsToStorage();
        this.showToast(`Switched to ${newTheme} mode`, 'success');
    }

    /**
     * Open settings modal
     */
    async openSettingsModal() {
        const modal = document.getElementById('settingsModal');

        // Populate form with current settings
        document.getElementById('themeSelect').value = this.settings.theme;
        document.getElementById('viewDensity').value = this.settings.viewDensity;
        document.getElementById('previewLines').value = this.settings.previewLines;

        // Reset system info to loading state
        this.resetSystemInfoLoadingState();

        // Load system information and backup status
        await Promise.all([
            this.loadSystemInfo(),
            this.checkBackupStatus()
        ]);

        this.openModal(modal);
    }

    /**
     * Reset system info fields to loading state
     */
    resetSystemInfoLoadingState() {
        Object.keys(MemoryDashboard.SYSTEM_INFO_CONFIG).forEach(id => {
            const element = document.getElementById(id);
            if (element) {
                element.textContent = 'Loading...';
            }
        });
    }

    /**
     * Load system information for settings modal
     */
    async loadSystemInfo() {
        try {
            // Use Promise.allSettled for robust error handling
            const [healthResult, detailedHealthResult] = await Promise.allSettled([
                this.apiCall('/health'),
                this.apiCall('/health/detailed')
            ]);

            const apiData = {
                health: healthResult.status === 'fulfilled' ? healthResult.value : null,
                detailedHealth: detailedHealthResult.status === 'fulfilled' ? detailedHealthResult.value : null
            };

            // Update fields using configuration
            Object.entries(MemoryDashboard.SYSTEM_INFO_CONFIG).forEach(([fieldId, config]) => {
                const element = document.getElementById(fieldId);
                if (!element) return;

                let value = null;
                for (const source of config.sources) {
                    const apiResponse = apiData[source.api];
                    if (apiResponse) {
                        value = this.getNestedValue(apiResponse, source.path);
                        if (value !== undefined && value !== null) break;
                    }
                }

                element.textContent = config.formatter(value);
            });

            // Log warnings for failed API calls
            if (healthResult.status === 'rejected') {
                console.warn('Failed to load health endpoint:', healthResult.reason);
            }
            if (detailedHealthResult.status === 'rejected') {
                console.warn('Failed to load detailed health endpoint:', detailedHealthResult.reason);
            }
        } catch (error) {
            console.error('Unexpected error loading system info:', error);
            // Set all system info fields that are still in loading state to error
            Object.keys(MemoryDashboard.SYSTEM_INFO_CONFIG).forEach(id => {
                const element = document.getElementById(id);
                if (element && element.textContent === 'Loading...') {
                    element.textContent = 'Error';
                }
            });
        }
    }

    /**
     * Get nested object value by path string
     * @param {Object} obj - Object to traverse
     * @param {string} path - Dot-separated path (e.g., 'storage.primary_stats.embedding_model')
     * @returns {*} Value at path or undefined
     */
    getNestedValue(obj, path) {
        return path.split('.').reduce((current, key) => current?.[key], obj);
    }

    /**
     * Format uptime seconds into human readable string
     * @param {number} seconds - Uptime in seconds
     * @returns {string} Formatted uptime string
     */
    static formatUptime(seconds) {
        const days = Math.floor(seconds / 86400);
        const hours = Math.floor((seconds % 86400) / 3600);
        const minutes = Math.floor((seconds % 3600) / 60);

        const parts = [];
        if (days > 0) parts.push(`${days}d`);
        if (hours > 0) parts.push(`${hours}h`);
        if (minutes > 0) parts.push(`${minutes}m`);

        return parts.length > 0 ? parts.join(' ') : '< 1m';
    }

    /**
     * Save settings from modal
     */
    saveSettings() {
        // Get values from form
        const theme = document.getElementById('themeSelect').value;
        const viewDensity = document.getElementById('viewDensity').value;
        const previewLines = parseInt(document.getElementById('previewLines').value, 10);

        // Update settings
        this.settings.theme = theme;
        this.settings.viewDensity = viewDensity;
        this.settings.previewLines = previewLines;

        // Apply changes
        this.applyTheme(theme);
        this.saveSettingsToStorage();

        // Close modal and show confirmation
        this.closeModal(document.getElementById('settingsModal'));
        this.showToast('Settings saved successfully', 'success');
    }

    // ===== MANAGE TAB METHODS =====

    /**
     * Load manage tab data
    */
    async loadManageData() {
    try {
            // Load tag statistics for bulk operations
            await this.loadTagSelectOptions();
            await this.loadTagManagementStats();
        } catch (error) {
            console.error('Failed to load manage data:', error);
            this.showToast('Failed to load management data', 'error');
        }
    }

    /**
     * Load tag options for bulk delete select
     */
    async loadTagSelectOptions() {
        try {
            const response = await fetch(`${this.apiBase}/manage/tags/stats`);
            if (!response.ok) throw new Error('Failed to load tags');

            const data = await response.json();
            const select = document.getElementById('deleteTagSelect');
            if (!select) return;

            // Clear existing options except the first
            while (select.children.length > 1) {
                select.removeChild(select.lastChild);
            }

            // Add tag options
            data.tags.forEach(tagStat => {
                const option = document.createElement('option');
                option.value = tagStat.tag;
                option.textContent = `${tagStat.tag} (${tagStat.count} memories)`;
                option.dataset.count = tagStat.count;  // Store count in data attribute
                select.appendChild(option);
            });
        } catch (error) {
            console.error('Failed to load tag options:', error);
        }
    }

    /**
     * Load tag management statistics
     */
    async loadTagManagementStats() {
        const container = document.getElementById('tagManagementContainer');
        if (!container) return;

        try {
            const response = await fetch(`${this.apiBase}/manage/tags/stats`);
            if (!response.ok) throw new Error('Failed to load tag stats');

            const data = await response.json();
            this.renderTagManagementTable(data);
        } catch (error) {
            console.error('Failed to load tag management stats:', error);
            container.innerHTML = '<p class="error">Failed to load tag statistics</p>';
        }
    }

    /**
     * Render tag management table
     */
    renderTagManagementTable(data) {
        const container = document.getElementById('tagManagementContainer');
        if (!container) return;

        let html = '<table class="tag-stats-table">';
        html += '<thead><tr>';
        html += '<th>Tag</th>';
        html += '<th>Count</th>';
        html += '<th>Actions</th>';
        html += '</tr></thead><tbody>';

        data.tags.forEach(tagStat => {
        html += '<tr>';
        html += `<td class="tag-name">${tagStat.tag}</td>`;
        html += `<td class="tag-count">${tagStat.count}</td>`;
        html += '<td class="tag-actions">';
        html += `<button class="tag-action-btn" data-action="rename-tag" data-tag="${this.escapeHtml(tagStat.tag)}">Rename</button>`;
        html += `<button class="tag-action-btn danger" data-action="delete-tag" data-tag="${this.escapeHtml(tagStat.tag)}" data-count="${tagStat.count}">Delete</button>`;
        html += '</td></tr>';
        });

        html += '</tbody></table>';
        container.innerHTML = html;

        // Add event listeners for tag actions
        container.addEventListener('click', (e) => {
            const button = e.target.closest('[data-action]');
            if (!button) return;

            const action = button.dataset.action;
            const tag = button.dataset.tag;

            if (action === 'rename-tag') {
                this.renameTag(tag);
            } else if (action === 'delete-tag') {
                const count = parseInt(button.dataset.count, 10);
                this.deleteTag(tag, count);
            }
        });
    }

    /**
     * Handle bulk delete by tag
     */
    async handleBulkDeleteByTag() {
        const select = document.getElementById('deleteTagSelect');
        const tag = select.value;

        if (!tag) {
            this.showToast('Please select a tag to delete', 'warning');
            return;
        }

        // Extract count from data attribute
        const option = select.querySelector(`option[value="${tag}"]`);
        const count = parseInt(option.dataset.count, 10) || 0;

        if (!await this.confirmBulkOperation(`Delete ${count} memories with tag "${tag}"?`)) {
            return;
        }

        this.setLoading(true);
        try {
            const response = await fetch(`${this.apiBase}/manage/bulk-delete`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    tag: tag,
                    confirm_count: count
                })
            });

            const result = await response.json();
            if (result.success) {
                this.showToast(result.message, 'success');
                await this.loadManageData(); // Refresh data
                await this.loadDashboardData(); // Refresh dashboard stats
            } else {
                this.showToast(result.message, 'error');
            }
        } catch (error) {
            console.error('Bulk delete failed:', error);
            this.showToast('Bulk delete operation failed', 'error');
        } finally {
            this.setLoading(false);
        }
    }

    /**
     * Handle cleanup duplicates
     */
    async handleCleanupDuplicates() {
        if (!await this.confirmBulkOperation('Remove all duplicate memories?')) {
            return;
        }

        this.setLoading(true);
        try {
            const response = await fetch(`${this.apiBase}/manage/cleanup-duplicates`, {
                method: 'POST'
            });

            const result = await response.json();
            if (result.success) {
                this.showToast(result.message, 'success');
                await this.loadManageData();
                await this.loadDashboardData();
            } else {
                this.showToast(result.message, 'error');
            }
        } catch (error) {
            console.error('Cleanup duplicates failed:', error);
            this.showToast('Cleanup operation failed', 'error');
        } finally {
            this.setLoading(false);
        }
    }

    /**
     * Handle bulk delete by date
     */
    async handleBulkDeleteByDate() {
        const dateInput = document.getElementById('deleteDateInput');
        const date = dateInput.value;

        if (!date) {
            this.showToast('Please select a date', 'warning');
            return;
        }

        if (!await this.confirmBulkOperation(`Delete all memories before ${date}?`)) {
            return;
        }

        this.setLoading(true);
        try {
            const response = await fetch(`${this.apiBase}/manage/bulk-delete`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    before_date: date
                })
            });

            const result = await response.json();
            if (result.success) {
                this.showToast(result.message, 'success');
                await this.loadManageData();
                await this.loadDashboardData();
            } else {
                this.showToast(result.message, 'error');
            }
        } catch (error) {
            console.error('Bulk delete by date failed:', error);
            this.showToast('Bulk delete operation failed', 'error');
        } finally {
            this.setLoading(false);
        }
    }

    /**
     * Handle database optimization
     */
    async handleOptimizeDatabase() {
        this.showToast('Database optimization not yet implemented', 'warning');
    }

    /**
     * Handle index rebuild
     */
    async handleRebuildIndex() {
        this.showToast('Index rebuild not yet implemented', 'warning');
    }

    /**
     * Rename a tag
     */
    async renameTag(oldTag) {
        const newTag = prompt(`Rename tag "${oldTag}" to:`, oldTag);
        if (!newTag || newTag === oldTag) return;

        this.showToast('Tag renaming not yet implemented', 'warning');
    }

    /**
     * Delete a tag
     */
    async deleteTag(tag, count) {
        if (!await this.confirmBulkOperation(`Delete tag "${tag}" from ${count} memories?`)) {
            return;
        }

        this.showToast('Tag deletion not yet implemented', 'warning');
    }

    // ===== ANALYTICS TAB METHODS =====

    /**
     * Load analytics tab data
     */
    async loadAnalyticsData() {
        try {
            await Promise.all([
                this.loadAnalyticsOverview(),
                this.loadMemoryGrowthChart(),
                this.loadTagUsageChart(),
                this.loadMemoryTypesChart(),
                this.loadActivityHeatmapChart(),
                this.loadTopTagsReport(),
                this.loadRecentActivityReport(),
                this.loadStorageReport()
            ]);
        } catch (error) {
            console.error('Failed to load analytics data:', error);
            this.showToast('Failed to load analytics data', 'error');
        }
    }

    /**
     * Load analytics overview metrics
     */
    async loadAnalyticsOverview() {
        try {
            const response = await fetch(`${this.apiBase}/analytics/overview`);
            if (!response.ok) throw new Error('Failed to load overview');

            const data = await response.json();

            // Update metric cards
            this.updateElementText('analyticsTotalMemories', data.total_memories || 0);
            this.updateElementText('analyticsThisWeek', data.memories_this_week || 0);
            this.updateElementText('analyticsUniqueTags', data.unique_tags || 0);
            this.updateElementText('analyticsDbSize', data.database_size_mb ?
                `${data.database_size_mb.toFixed(1)} MB` : 'N/A');
        } catch (error) {
            console.error('Failed to load analytics overview:', error);
        }
    }

    /**
     * Load memory growth chart
     */
    async loadMemoryGrowthChart() {
        const container = document.getElementById('memoryGrowthChart');
        const period = document.getElementById('growthPeriodSelect').value;

        if (!container) return;

        try {
            const response = await fetch(`${this.apiBase}/analytics/memory-growth?period=${period}`);
            if (!response.ok) throw new Error('Failed to load growth data');

            const data = await response.json();
            this.renderMemoryGrowthChart(container, data);
        } catch (error) {
            console.error('Failed to load memory growth:', error);
            container.innerHTML = '<p class="error">Failed to load growth chart</p>';
        }
    }

    /**
     * Render memory growth chart
     */
    renderMemoryGrowthChart(container, data) {
        if (!data.data_points || data.data_points.length === 0) {
            container.innerHTML = '<p>No growth data available</p>';
            return;
        }

        // Find max count for scaling
        const recentPoints = data.data_points.slice(-10);
        const maxCount = Math.max(...recentPoints.map(p => p.count), 1);

        let html = '<div class="simple-chart">';

        recentPoints.forEach(point => {
            // Normalize bar width relative to max, then convert to pixels (200px scale)
            const barWidthPx = (point.count / maxCount) * 200;
            const displayCount = point.count || 0;
            const displayCumulative = point.cumulative || 0;
            // Use label if available, otherwise fall back to date for backward compatibility
            const displayLabel = point.label || point.date;

            html += `<div class="chart-row">
                <div class="chart-bar" style="width: ${barWidthPx}px"></div>
                <span class="chart-value">+${displayCount} <small>(${displayCumulative} total)</small></span>
                <span class="chart-label">${displayLabel}</span>
            </div>`;
        });

        html += '</div>';
        container.innerHTML = html;
    }

    /**
     * Load tag usage chart
     */
    async loadTagUsageChart() {
        const container = document.getElementById('tagUsageChart');
        if (!container) return;

        try {
            const response = await fetch(`${this.apiBase}/analytics/tag-usage`);
            if (!response.ok) throw new Error('Failed to load tag usage');

            const data = await response.json();
            this.renderTagUsageChart(container, data);
        } catch (error) {
            console.error('Failed to load tag usage:', error);
            container.innerHTML = '<p class="error">Failed to load tag usage chart</p>';
        }
    }

    /**
     * Render tag usage chart
     */
    renderTagUsageChart(container, data) {
        if (!data.tags || data.tags.length === 0) {
            container.innerHTML = '<p>No tags found</p>';
            return;
        }

        // Filter tags with >10 memories, aggregate the rest
        const significantTags = data.tags.filter(t => t.count > 10);
        const minorTags = data.tags.filter(t => t.count <= 10);

        let html = '<div class="simple-chart">';

        // Render significant tags
        significantTags.forEach(tag => {
            const barWidthPx = (tag.percentage / 100) * 200; // Convert percentage to pixels (200px scale)
            html += `<div class="chart-row">
                <div class="chart-bar" style="width: ${barWidthPx}px"></div>
                <span class="chart-value">${tag.count} (${tag.percentage}%)</span>
                <span class="chart-label">${tag.tag}</span>
            </div>`;
        });

        // Add "diverse" category if there are minor tags
        if (minorTags.length > 0) {
            const diverseCount = minorTags.reduce((sum, t) => sum + t.count, 0);
            const diversePercentage = minorTags.reduce((sum, t) => sum + t.percentage, 0);
            const barWidthPx = (diversePercentage / 100) * 200;
            html += `<div class="chart-row">
                <div class="chart-bar" style="width: ${barWidthPx}px"></div>
                <span class="chart-value">${diverseCount} (${diversePercentage.toFixed(1)}%)</span>
                <span class="chart-label" title="${minorTags.length} tags with ≤10 memories each">diverse (${minorTags.length} tags)</span>
            </div>`;
        }

        html += '</div>';
        container.innerHTML = html;
    }

    /**
     * Load memory types chart
     */
    async loadMemoryTypesChart() {
        const container = document.getElementById('memoryTypesChart');
        if (!container) return;

        try {
            const response = await fetch(`${this.apiBase}/analytics/memory-types`);
            if (!response.ok) throw new Error('Failed to load memory types');

            const data = await response.json();
            this.renderMemoryTypesChart(container, data);
        } catch (error) {
            console.error('Failed to load memory types:', error);
            container.innerHTML = '<p class="error">Failed to load memory types chart</p>';
        }
    }

    /**
     * Render memory types chart
     */
    renderMemoryTypesChart(container, data) {
        if (!data.types || data.types.length === 0) {
            container.innerHTML = '<p>No memory types found</p>';
            return;
        }

        // Filter types with >10 memories, aggregate the rest
        const significantTypes = data.types.filter(t => t.count > 10);
        const minorTypes = data.types.filter(t => t.count <= 10);

        let html = '<div class="simple-chart">';

        // Render significant types
        significantTypes.forEach(type => {
            const barWidthPx = (type.percentage / 100) * 200; // Convert percentage to pixels (200px scale)
            const typeName = type.memory_type || 'untyped';
            html += `<div class="chart-row">
                <div class="chart-bar" style="width: ${barWidthPx}px"></div>
                <span class="chart-value">${type.count} (${type.percentage.toFixed(1)}%)</span>
                <span class="chart-label" title="${typeName}">${typeName}</span>
            </div>`;
        });

        // Add "diverse" category if there are minor types
        if (minorTypes.length > 0) {
            const diverseCount = minorTypes.reduce((sum, t) => sum + t.count, 0);
            const diversePercentage = minorTypes.reduce((sum, t) => sum + t.percentage, 0);
            const barWidthPx = (diversePercentage / 100) * 200;
            html += `<div class="chart-row">
                <div class="chart-bar" style="width: ${barWidthPx}px"></div>
                <span class="chart-value">${diverseCount} (${diversePercentage.toFixed(1)}%)</span>
                <span class="chart-label" title="${minorTypes.length} types with ≤10 memories each">diverse (${minorTypes.length} types)</span>
            </div>`;
        }

        html += '</div>';
        container.innerHTML = html;
    }

    /**
    * Load top tags report
    */
    async loadTopTagsReport() {
    const container = document.getElementById('topTagsList');
    const period = document.getElementById('topTagsPeriodSelect')?.value || '30d';
        if (!container) return;

    try {
    const response = await fetch(`${this.apiBase}/analytics/top-tags?period=${period}`);
            if (!response.ok) throw new Error('Failed to load top tags');

    const data = await response.json();
        this.renderTopTagsReport(container, data);
    } catch (error) {
    console.error('Failed to load top tags:', error);
        container.innerHTML = '<p class="error">Failed to load top tags</p>';
        }
    }

    /**
    * Render top tags report
    */
    renderTopTagsReport(container, data) {
    if (!data.tags || data.tags.length === 0) {
    container.innerHTML = '<p>No tags found</p>';
    return;
    }

    let html = '<div class="enhanced-tags-report">';
    html += `<div class="report-period">Period: ${data.period}</div>`;
    html += '<ul class="tags-list">';
    data.tags.slice(0, 10).forEach(tag => {
        const trendIcon = tag.trending ? '📈' : '';
            const growthText = tag.growth_rate !== null ? ` (${tag.growth_rate > 0 ? '+' : ''}${tag.growth_rate}%)` : '';
        html += `<li>
                <div class="tag-header">
                    <strong>${tag.tag}</strong>${trendIcon}
                    <span class="tag-count">${tag.count} memories (${tag.percentage}%)${growthText}</span>
                </div>`;
            if (tag.co_occurring_tags && tag.co_occurring_tags.length > 0) {
                html += '<div class="tag-cooccurrence">Often with: ';
                html += tag.co_occurring_tags.slice(0, 3).map(co => `${co.tag} (${co.strength.toFixed(2)})`).join(', ');
                html += '</div>';
            }
            html += '</li>';
        });
        html += '</ul></div>';

        container.innerHTML = html;
    }

    /**
    * Load recent activity report
    */
    async loadRecentActivityReport() {
    const container = document.getElementById('recentActivityList');
    const granularity = document.getElementById('activityGranularitySelect')?.value || 'daily';
        if (!container) return;

    try {
    const response = await fetch(`${this.apiBase}/analytics/activity-breakdown?granularity=${granularity}`);
    if (!response.ok) throw new Error('Failed to load activity breakdown');

    const data = await response.json();
    this.renderRecentActivityReport(container, data);
    } catch (error) {
    console.error('Failed to load recent activity:', error);
    container.innerHTML = '<p class="error">Failed to load recent activity</p>';
    }
    }

    /**
    * Render recent activity report
    */
    renderRecentActivityReport(container, data) {
    let html = '<div class="activity-breakdown">';

    // Summary stats
    html += '<div class="activity-summary">';
        html += `<div class="activity-stat"><strong>Active Days:</strong> ${data.active_days}/${data.total_days}</div>`;
    html += `<div class="activity-stat"><strong>Current Streak:</strong> ${data.current_streak} days</div>`;
    html += `<div class="activity-stat"><strong>Longest Streak:</strong> ${data.longest_streak} days</div>`;
    html += '</div>';

    if (data.peak_times && data.peak_times.length > 0) {
    html += '<div class="peak-times">';
        html += '<strong>Peak Times:</strong> ';
        html += data.peak_times.join(', ');
            html += '</div>';
    }

        // Activity breakdown chart
        if (data.breakdown && data.breakdown.length > 0) {
            html += '<div class="activity-chart">';
            // Calculate total count for percentage-based distribution
            const totalCount = data.breakdown.reduce((sum, d) => sum + d.count, 0);
            const maxCount = Math.max(...data.breakdown.map(d => d.count));

            data.breakdown.forEach(item => {
                // Show percentage of total activity, with minimum width for visibility
                const percentage = totalCount > 0 ? (item.count / totalCount * 100) : 0;
                // Use percentage for bar width, but scale up for better visualization
                // Use max count for scaling to ensure largest bar reaches reasonable width
                const barWidth = totalCount > 0 ? (item.count / maxCount * 100) : 0;

                html += `<div class="activity-bar-row">
                    <span class="activity-label">${item.label}</span>
                    <div class="activity-bar" style="width: ${barWidth}%" title="${item.count} memories (${percentage.toFixed(1)}%)"></div>
                    <span class="activity-count">${item.count} (${percentage.toFixed(1)}%)</span>
                </div>`;
            });

            html += '</div>';
        }

        html += '</div>';
        container.innerHTML = html;
    }

    /**
    * Load activity heatmap chart
    */
    async loadActivityHeatmapChart() {
    const container = document.getElementById('activityHeatmapChart');
        const period = document.getElementById('heatmapPeriodSelect').value;

        if (!container) return;

        try {
            const response = await fetch(`${this.apiBase}/analytics/activity-heatmap?days=${period}`);
            if (!response.ok) throw new Error('Failed to load heatmap data');

            const data = await response.json();
            this.renderActivityHeatmapChart(container, data);
        } catch (error) {
            console.error('Failed to load activity heatmap:', error);
            container.innerHTML = '<p class="error">Failed to load activity heatmap</p>';
        }
    }

    /**
     * Render activity heatmap chart
     */
    renderActivityHeatmapChart(container, data) {
        if (!data.data || data.data.length === 0) {
            container.innerHTML = '<p>No activity data available</p>';
            return;
        }

        // Create calendar grid
        let html = '<div class="activity-heatmap">';
        html += '<div class="heatmap-stats">';
        html += `<span>${data.total_days} active days</span>`;
        html += `<span>Max: ${data.max_count} memories/day</span>`;
        html += '</div>';

        // Group by months
        const months = {};
        data.data.forEach(day => {
            const date = new Date(day.date);
            const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
            if (!months[monthKey]) {
                months[monthKey] = [];
            }
            months[monthKey].push(day);
        });

        // Render each month
        Object.keys(months).sort().reverse().forEach(monthKey => {
            const [year, month] = monthKey.split('-');
            const monthName = new Date(year, month - 1).toLocaleString('default', { month: 'short' });

            html += `<div class="heatmap-month">`;
            html += `<div class="month-label">${monthName} ${year}</div>`;
            html += '<div class="month-grid">';

            // Create 7x6 grid (weeks x days)
            const monthData = months[monthKey];
            // Parse date in local timezone to avoid day-of-week shifting near timezone boundaries
            const [fYear, fMonth, fDay] = monthData[0].date.split('-').map(Number);
            const firstDay = new Date(fYear, fMonth - 1, fDay).getDay();

            // Add empty cells for days before month starts
            for (let i = 0; i < firstDay; i++) {
                html += '<div class="heatmap-cell empty"></div>';
            }

            // Add cells for each day
            monthData.forEach(day => {
                const level = day.level;
                const tooltip = `${day.date}: ${day.count} memories`;
                html += `<div class="heatmap-cell level-${level}" title="${tooltip}"></div>`;
            });

            html += '</div></div>';
        });

        // Legend
        html += '<div class="heatmap-legend">';
        html += '<span>Less</span>';
        for (let i = 0; i <= 4; i++) {
            html += `<div class="legend-cell level-${i}"></div>`;
        }
        html += '<span>More</span>';
        html += '</div>';

        html += '</div>';
        container.innerHTML = html;
    }

    /**
     * Handle heatmap period change
     */
    async handleHeatmapPeriodChange() {
        await this.loadActivityHeatmapChart();
    }

    /**
     * Handle top tags period change
     */
    async handleTopTagsPeriodChange() {
        await this.loadTopTagsReport();
    }

    /**
     * Handle activity granularity change
     */
    async handleActivityGranularityChange() {
        await this.loadRecentActivityReport();
    }

    /**
     * Load storage report
     */
    async loadStorageReport() {
        const container = document.getElementById('storageReport');
        if (!container) return;

        try {
            const response = await fetch(`${this.apiBase}/analytics/storage-stats`);
            if (!response.ok) throw new Error('Failed to load storage stats');

            const data = await response.json();
            this.renderStorageReport(container, data);
        } catch (error) {
            console.error('Failed to load storage report:', error);
            container.innerHTML = '<p class="error">Failed to load storage report</p>';
        }
    }

    /**
     * Render storage report
     */
    renderStorageReport(container, data) {
        let html = '<div class="storage-report">';

        // Summary stats
        html += '<div class="storage-summary">';
        html += `<div class="storage-stat"><strong>Total Size:</strong> ${data.total_size_mb} MB</div>`;
        html += `<div class="storage-stat"><strong>Average Memory:</strong> ${data.average_memory_size} chars</div>`;
        html += `<div class="storage-stat"><strong>Efficiency:</strong> ${data.storage_efficiency}%</div>`;
        html += '</div>';

        // Largest memories
        if (data.largest_memories && data.largest_memories.length > 0) {
            html += '<h4>Largest Memories</h4>';
            html += '<ul class="largest-memories">';
            data.largest_memories.slice(0, 5).forEach(memory => {
                // Backend provides created_at as ISO string, not timestamp
                const date = memory.created_at ? new Date(memory.created_at).toLocaleDateString() : 'Unknown';
                // Backend provides size_kb and preview (not size and content_preview)
                const sizeDisplay = memory.size_kb ? `${memory.size_kb} KB` : `${memory.size_bytes || 0} bytes`;
                html += `<li>
                    <div class="memory-size">${sizeDisplay}</div>
                    <div class="memory-preview">${this.escapeHtml(memory.preview || '')}</div>
                    <div class="memory-meta">${date} • Tags: ${memory.tags.join(', ') || 'none'}</div>
                </li>`;
            });
            html += '</ul>';
        }

        html += '</div>';
        container.innerHTML = html;
    }

    /**
     * Handle growth period change
     */
    async handleGrowthPeriodChange() {
        await this.loadMemoryGrowthChart();
    }

    // ===== UTILITY METHODS =====

    /**
     * Show confirmation dialog for bulk operations
     */
    async confirmBulkOperation(message) {
        return confirm(`⚠️ WARNING: ${message}

This action cannot be undone. Are you sure?`);
    }

    /**
     * Update element text content
     */
    updateElementText(elementId, text) {
        const element = document.getElementById(elementId);
        if (element) {
            element.textContent = text;
        }
    }

    /**
     * Cleanup when page unloads
     */
    destroy() {
        if (this.eventSource) {
            this.eventSource.close();
        }
    }
}

// Initialize the application when DOM is ready
console.log('⚡ Registering DOMContentLoaded listener');
document.addEventListener('DOMContentLoaded', () => {
    console.log('⚡ DOMContentLoaded fired - Creating MemoryDashboard instance');
    window.app = new MemoryDashboard();
    console.log('⚡ MemoryDashboard created, window.app =', window.app);
});

// Cleanup on page unload
window.addEventListener('beforeunload', () => {
    if (window.app) {
        window.app.destroy();
    }
});
```
Page 33/35FirstPrevNextLast