claude-mpm 5.4.36__py3-none-any.whl → 5.4.62__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +5 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +489 -177
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/frontmatter_validator.py +2 -2
- claude_mpm/cli/commands/configure_agent_display.py +12 -0
- claude_mpm/cli/commands/mpm_init/core.py +72 -0
- claude_mpm/cli/commands/profile.py +276 -0
- claude_mpm/cli/commands/skills.py +14 -18
- claude_mpm/cli/executor.py +10 -0
- claude_mpm/cli/parsers/base_parser.py +7 -0
- claude_mpm/cli/parsers/profile_parser.py +147 -0
- claude_mpm/cli/parsers/skills_parser.py +0 -6
- claude_mpm/cli/startup.py +433 -147
- claude_mpm/commands/mpm-config.md +13 -250
- claude_mpm/commands/mpm-doctor.md +9 -22
- claude_mpm/commands/mpm-help.md +5 -206
- claude_mpm/commands/mpm-init.md +81 -507
- claude_mpm/commands/mpm-monitor.md +15 -402
- claude_mpm/commands/mpm-organize.md +61 -441
- claude_mpm/commands/mpm-postmortem.md +6 -108
- claude_mpm/commands/mpm-session-resume.md +12 -363
- claude_mpm/commands/mpm-status.md +5 -69
- claude_mpm/commands/mpm-ticket-view.md +52 -495
- claude_mpm/commands/mpm-version.md +5 -107
- claude_mpm/core/optimized_startup.py +61 -0
- claude_mpm/core/shared/config_loader.py +3 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CWc5urbQ.js → 4TdZjIqw.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uj46x2Wr.js → BSNlmTZj.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DjhvlsAc.js → NqQ1dWOy.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{N4qtv3Hx.js → Vzk33B_K.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.CAGBuiOw.js → 0.m1gL8KXf.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +10 -10
- claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
- claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
- claude_mpm/hooks/kuzu_memory_hook.py +5 -5
- claude_mpm/init.py +276 -0
- claude_mpm/services/agents/agent_builder.py +3 -3
- claude_mpm/services/agents/deployment/agent_deployment.py +22 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +3 -1
- claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
- claude_mpm/services/agents/deployment/agent_template_builder.py +37 -17
- claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
- claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +149 -4
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +47 -26
- claude_mpm/services/agents/git_source_manager.py +21 -2
- claude_mpm/services/agents/sources/git_source_sync_service.py +116 -5
- claude_mpm/services/monitor/management/lifecycle.py +7 -1
- claude_mpm/services/pm_skills_deployer.py +711 -0
- claude_mpm/services/profile_manager.py +337 -0
- claude_mpm/services/skills/git_skill_source_manager.py +148 -11
- claude_mpm/services/skills/selective_skill_deployer.py +97 -48
- claude_mpm/services/skills_deployer.py +161 -65
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- claude_mpm/skills/bundled/pm/pm-delegation-patterns/SKILL.md +167 -0
- claude_mpm/skills/bundled/pm/pm-git-file-tracking/SKILL.md +113 -0
- claude_mpm/skills/bundled/pm/pm-pr-workflow/SKILL.md +124 -0
- claude_mpm/skills/bundled/pm/pm-ticketing-integration/SKILL.md +154 -0
- claude_mpm/skills/bundled/pm/pm-verification-protocols/SKILL.md +198 -0
- claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/security-scanning.md +112 -0
- claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
- claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
- claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
- claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
- claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
- claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
- claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
- claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
- claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
- claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
- claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- claude_mpm/skills/skill_manager.py +98 -3
- claude_mpm/templates/.pre-commit-config.yaml +112 -0
- {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.62.dist-info}/METADATA +3 -2
- {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.62.dist-info}/RECORD +244 -68
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +0 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +0 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +0 -10
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.62.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.62.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.62.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.62.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.36.dist-info → claude_mpm-5.4.62.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,901 @@
|
|
|
1
|
+
# Architecture Patterns
|
|
2
|
+
|
|
3
|
+
Desktop-specific architectural patterns for building maintainable, scalable Rust applications with clear separation of concerns.
|
|
4
|
+
|
|
5
|
+
## Core Architectural Patterns
|
|
6
|
+
|
|
7
|
+
### MVC (Model-View-Controller)
|
|
8
|
+
|
|
9
|
+
Traditional pattern adapted for desktop applications.
|
|
10
|
+
|
|
11
|
+
```rust
|
|
12
|
+
// Model - Application state and business logic
|
|
13
|
+
mod model {
|
|
14
|
+
use serde::{Deserialize, Serialize};
|
|
15
|
+
|
|
16
|
+
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
17
|
+
pub struct User {
|
|
18
|
+
pub id: u64,
|
|
19
|
+
pub name: String,
|
|
20
|
+
pub email: String,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[derive(Clone, Debug)]
|
|
24
|
+
pub struct UserModel {
|
|
25
|
+
users: Vec<User>,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
impl UserModel {
|
|
29
|
+
pub fn new() -> Self {
|
|
30
|
+
Self { users: Vec::new() }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub fn add_user(&mut self, user: User) {
|
|
34
|
+
self.users.push(user);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub fn remove_user(&mut self, id: u64) {
|
|
38
|
+
self.users.retain(|u| u.id != id);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub fn get_users(&self) -> &[User] {
|
|
42
|
+
&self.users
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
pub fn find_user(&self, id: u64) -> Option<&User> {
|
|
46
|
+
self.users.iter().find(|u| u.id == id)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Controller - Handles user input and updates model
|
|
52
|
+
mod controller {
|
|
53
|
+
use super::model::{User, UserModel};
|
|
54
|
+
|
|
55
|
+
pub struct UserController {
|
|
56
|
+
model: UserModel,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
impl UserController {
|
|
60
|
+
pub fn new(model: UserModel) -> Self {
|
|
61
|
+
Self { model }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub fn create_user(&mut self, name: String, email: String) -> Result<(), String> {
|
|
65
|
+
// Validation
|
|
66
|
+
if name.is_empty() {
|
|
67
|
+
return Err("Name cannot be empty".to_string());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let id = self.model.get_users().len() as u64 + 1;
|
|
71
|
+
let user = User { id, name, email };
|
|
72
|
+
|
|
73
|
+
self.model.add_user(user);
|
|
74
|
+
Ok(())
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pub fn delete_user(&mut self, id: u64) -> Result<(), String> {
|
|
78
|
+
if self.model.find_user(id).is_none() {
|
|
79
|
+
return Err("User not found".to_string());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
self.model.remove_user(id);
|
|
83
|
+
Ok(())
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pub fn get_model(&self) -> &UserModel {
|
|
87
|
+
&self.model
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// View - UI rendering (Tauri example)
|
|
93
|
+
#[tauri::command]
|
|
94
|
+
fn get_users(controller: tauri::State<UserController>) -> Vec<User> {
|
|
95
|
+
controller.get_model().get_users().to_vec()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#[tauri::command]
|
|
99
|
+
fn create_user(
|
|
100
|
+
controller: tauri::State<UserController>,
|
|
101
|
+
name: String,
|
|
102
|
+
email: String,
|
|
103
|
+
) -> Result<(), String> {
|
|
104
|
+
controller.inner().lock().unwrap().create_user(name, email)
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### MVVM (Model-View-ViewModel)
|
|
109
|
+
|
|
110
|
+
Better for reactive UIs with two-way data binding.
|
|
111
|
+
|
|
112
|
+
```rust
|
|
113
|
+
use std::sync::{Arc, Mutex};
|
|
114
|
+
use tokio::sync::broadcast;
|
|
115
|
+
|
|
116
|
+
// Model - Business data
|
|
117
|
+
#[derive(Clone, Debug)]
|
|
118
|
+
pub struct TodoItem {
|
|
119
|
+
pub id: u64,
|
|
120
|
+
pub title: String,
|
|
121
|
+
pub completed: bool,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
pub struct TodoModel {
|
|
125
|
+
items: Vec<TodoItem>,
|
|
126
|
+
next_id: u64,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
impl TodoModel {
|
|
130
|
+
pub fn new() -> Self {
|
|
131
|
+
Self {
|
|
132
|
+
items: Vec::new(),
|
|
133
|
+
next_id: 1,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
pub fn add_item(&mut self, title: String) -> TodoItem {
|
|
138
|
+
let item = TodoItem {
|
|
139
|
+
id: self.next_id,
|
|
140
|
+
title,
|
|
141
|
+
completed: false,
|
|
142
|
+
};
|
|
143
|
+
self.next_id += 1;
|
|
144
|
+
self.items.push(item.clone());
|
|
145
|
+
item
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
pub fn toggle_item(&mut self, id: u64) -> Option<bool> {
|
|
149
|
+
self.items
|
|
150
|
+
.iter_mut()
|
|
151
|
+
.find(|item| item.id == id)
|
|
152
|
+
.map(|item| {
|
|
153
|
+
item.completed = !item.completed;
|
|
154
|
+
item.completed
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
pub fn get_items(&self) -> &[TodoItem] {
|
|
159
|
+
&self.items
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ViewModel - Presentation logic and state
|
|
164
|
+
pub struct TodoViewModel {
|
|
165
|
+
model: Arc<Mutex<TodoModel>>,
|
|
166
|
+
change_notifier: broadcast::Sender<ViewModelEvent>,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[derive(Clone, Debug)]
|
|
170
|
+
pub enum ViewModelEvent {
|
|
171
|
+
ItemAdded(TodoItem),
|
|
172
|
+
ItemToggled(u64, bool),
|
|
173
|
+
ItemsChanged,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
impl TodoViewModel {
|
|
177
|
+
pub fn new() -> Self {
|
|
178
|
+
let (tx, _rx) = broadcast::channel(100);
|
|
179
|
+
Self {
|
|
180
|
+
model: Arc::new(Mutex::new(TodoModel::new())),
|
|
181
|
+
change_notifier: tx,
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
pub fn add_todo(&self, title: String) -> Result<(), String> {
|
|
186
|
+
if title.trim().is_empty() {
|
|
187
|
+
return Err("Title cannot be empty".to_string());
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let mut model = self.model.lock().unwrap();
|
|
191
|
+
let item = model.add_item(title);
|
|
192
|
+
drop(model);
|
|
193
|
+
|
|
194
|
+
let _ = self.change_notifier.send(ViewModelEvent::ItemAdded(item));
|
|
195
|
+
Ok(())
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
pub fn toggle_todo(&self, id: u64) -> Result<(), String> {
|
|
199
|
+
let mut model = self.model.lock().unwrap();
|
|
200
|
+
let completed = model
|
|
201
|
+
.toggle_item(id)
|
|
202
|
+
.ok_or("Item not found".to_string())?;
|
|
203
|
+
drop(model);
|
|
204
|
+
|
|
205
|
+
let _ = self
|
|
206
|
+
.change_notifier
|
|
207
|
+
.send(ViewModelEvent::ItemToggled(id, completed));
|
|
208
|
+
Ok(())
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
pub fn get_todos(&self) -> Vec<TodoItem> {
|
|
212
|
+
self.model.lock().unwrap().get_items().to_vec()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
pub fn subscribe(&self) -> broadcast::Receiver<ViewModelEvent> {
|
|
216
|
+
self.change_notifier.subscribe()
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// View - Tauri commands
|
|
221
|
+
#[tauri::command]
|
|
222
|
+
async fn add_todo(viewmodel: tauri::State<'_, TodoViewModel>, title: String) -> Result<(), String> {
|
|
223
|
+
viewmodel.add_todo(title)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#[tauri::command]
|
|
227
|
+
async fn toggle_todo(viewmodel: tauri::State<'_, TodoViewModel>, id: u64) -> Result<(), String> {
|
|
228
|
+
viewmodel.toggle_todo(id)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#[tauri::command]
|
|
232
|
+
async fn get_todos(viewmodel: tauri::State<'_, TodoViewModel>) -> Vec<TodoItem> {
|
|
233
|
+
viewmodel.get_todos()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Setup with change notifications
|
|
237
|
+
fn main() {
|
|
238
|
+
let viewmodel = TodoViewModel::new();
|
|
239
|
+
let mut rx = viewmodel.subscribe();
|
|
240
|
+
|
|
241
|
+
// Background task to push updates to frontend
|
|
242
|
+
tokio::spawn(async move {
|
|
243
|
+
while let Ok(event) = rx.recv().await {
|
|
244
|
+
// Emit event to frontend
|
|
245
|
+
println!("ViewModel changed: {:?}", event);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
tauri::Builder::default()
|
|
250
|
+
.manage(viewmodel)
|
|
251
|
+
.invoke_handler(tauri::generate_handler![add_todo, toggle_todo, get_todos])
|
|
252
|
+
.run(tauri::generate_context!())
|
|
253
|
+
.expect("error while running tauri application");
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Command Pattern
|
|
258
|
+
|
|
259
|
+
Encapsulate actions as objects for undo/redo functionality.
|
|
260
|
+
|
|
261
|
+
```rust
|
|
262
|
+
use std::fmt;
|
|
263
|
+
|
|
264
|
+
// Command trait
|
|
265
|
+
pub trait Command: fmt::Debug {
|
|
266
|
+
fn execute(&mut self, app: &mut Application) -> Result<(), String>;
|
|
267
|
+
fn undo(&mut self, app: &mut Application) -> Result<(), String>;
|
|
268
|
+
fn description(&self) -> String;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Application state
|
|
272
|
+
pub struct Application {
|
|
273
|
+
pub text: String,
|
|
274
|
+
pub cursor: usize,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Concrete commands
|
|
278
|
+
#[derive(Debug)]
|
|
279
|
+
pub struct InsertTextCommand {
|
|
280
|
+
text: String,
|
|
281
|
+
position: usize,
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
impl Command for InsertTextCommand {
|
|
285
|
+
fn execute(&mut self, app: &mut Application) -> Result<(), String> {
|
|
286
|
+
app.text.insert_str(self.position, &self.text);
|
|
287
|
+
app.cursor = self.position + self.text.len();
|
|
288
|
+
Ok(())
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
fn undo(&mut self, app: &mut Application) -> Result<(), String> {
|
|
292
|
+
let start = self.position;
|
|
293
|
+
let end = self.position + self.text.len();
|
|
294
|
+
app.text.drain(start..end);
|
|
295
|
+
app.cursor = self.position;
|
|
296
|
+
Ok(())
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
fn description(&self) -> String {
|
|
300
|
+
format!("Insert '{}'", self.text)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
#[derive(Debug)]
|
|
305
|
+
pub struct DeleteTextCommand {
|
|
306
|
+
deleted_text: String,
|
|
307
|
+
position: usize,
|
|
308
|
+
length: usize,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
impl Command for DeleteTextCommand {
|
|
312
|
+
fn execute(&mut self, app: &mut Application) -> Result<(), String> {
|
|
313
|
+
let start = self.position;
|
|
314
|
+
let end = self.position + self.length;
|
|
315
|
+
self.deleted_text = app.text.drain(start..end).collect();
|
|
316
|
+
app.cursor = self.position;
|
|
317
|
+
Ok(())
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
fn undo(&mut self, app: &mut Application) -> Result<(), String> {
|
|
321
|
+
app.text.insert_str(self.position, &self.deleted_text);
|
|
322
|
+
app.cursor = self.position + self.deleted_text.len();
|
|
323
|
+
Ok(())
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
fn description(&self) -> String {
|
|
327
|
+
format!("Delete {} characters", self.length)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Command manager with undo/redo
|
|
332
|
+
pub struct CommandManager {
|
|
333
|
+
history: Vec<Box<dyn Command>>,
|
|
334
|
+
current: usize,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
impl CommandManager {
|
|
338
|
+
pub fn new() -> Self {
|
|
339
|
+
Self {
|
|
340
|
+
history: Vec::new(),
|
|
341
|
+
current: 0,
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
pub fn execute(&mut self, mut command: Box<dyn Command>, app: &mut Application) -> Result<(), String> {
|
|
346
|
+
command.execute(app)?;
|
|
347
|
+
|
|
348
|
+
// Clear redo history
|
|
349
|
+
self.history.truncate(self.current);
|
|
350
|
+
self.history.push(command);
|
|
351
|
+
self.current += 1;
|
|
352
|
+
|
|
353
|
+
Ok(())
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
pub fn undo(&mut self, app: &mut Application) -> Result<(), String> {
|
|
357
|
+
if self.current == 0 {
|
|
358
|
+
return Err("Nothing to undo".to_string());
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
self.current -= 1;
|
|
362
|
+
self.history[self.current].undo(app)?;
|
|
363
|
+
Ok(())
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
pub fn redo(&mut self, app: &mut Application) -> Result<(), String> {
|
|
367
|
+
if self.current >= self.history.len() {
|
|
368
|
+
return Err("Nothing to redo".to_string());
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
self.history[self.current].execute(app)?;
|
|
372
|
+
self.current += 1;
|
|
373
|
+
Ok(())
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
pub fn can_undo(&self) -> bool {
|
|
377
|
+
self.current > 0
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
pub fn can_redo(&self) -> bool {
|
|
381
|
+
self.current < self.history.len()
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
pub fn get_history(&self) -> Vec<String> {
|
|
385
|
+
self.history
|
|
386
|
+
.iter()
|
|
387
|
+
.take(self.current)
|
|
388
|
+
.map(|cmd| cmd.description())
|
|
389
|
+
.collect()
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Tauri integration
|
|
394
|
+
use std::sync::Mutex;
|
|
395
|
+
|
|
396
|
+
struct AppState {
|
|
397
|
+
app: Mutex<Application>,
|
|
398
|
+
commands: Mutex<CommandManager>,
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
#[tauri::command]
|
|
402
|
+
fn insert_text(state: tauri::State<AppState>, text: String, position: usize) -> Result<(), String> {
|
|
403
|
+
let mut app = state.app.lock().unwrap();
|
|
404
|
+
let mut commands = state.commands.lock().unwrap();
|
|
405
|
+
|
|
406
|
+
let command = Box::new(InsertTextCommand { text, position });
|
|
407
|
+
commands.execute(command, &mut app)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
#[tauri::command]
|
|
411
|
+
fn undo(state: tauri::State<AppState>) -> Result<(), String> {
|
|
412
|
+
let mut app = state.app.lock().unwrap();
|
|
413
|
+
let mut commands = state.commands.lock().unwrap();
|
|
414
|
+
commands.undo(&mut app)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
#[tauri::command]
|
|
418
|
+
fn redo(state: tauri::State<AppState>) -> Result<(), String> {
|
|
419
|
+
let mut app = state.app.lock().unwrap();
|
|
420
|
+
let mut commands = state.commands.lock().unwrap();
|
|
421
|
+
commands.redo(&mut app)
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Event-Driven Architecture
|
|
426
|
+
|
|
427
|
+
### Event Bus Pattern
|
|
428
|
+
|
|
429
|
+
```rust
|
|
430
|
+
use std::collections::HashMap;
|
|
431
|
+
use std::sync::{Arc, Mutex};
|
|
432
|
+
use tokio::sync::mpsc;
|
|
433
|
+
|
|
434
|
+
// Event types
|
|
435
|
+
#[derive(Clone, Debug)]
|
|
436
|
+
pub enum AppEvent {
|
|
437
|
+
UserLoggedIn { user_id: u64, username: String },
|
|
438
|
+
FileOpened { path: String },
|
|
439
|
+
DataChanged { entity: String, id: u64 },
|
|
440
|
+
ErrorOccurred { message: String },
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Event handler trait
|
|
444
|
+
pub trait EventHandler: Send + Sync {
|
|
445
|
+
fn handle(&self, event: &AppEvent);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Event bus
|
|
449
|
+
pub struct EventBus {
|
|
450
|
+
handlers: Arc<Mutex<HashMap<String, Vec<Arc<dyn EventHandler>>>>>,
|
|
451
|
+
sender: mpsc::UnboundedSender<AppEvent>,
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
impl EventBus {
|
|
455
|
+
pub fn new() -> Self {
|
|
456
|
+
let handlers = Arc::new(Mutex::new(HashMap::new()));
|
|
457
|
+
let handlers_clone = handlers.clone();
|
|
458
|
+
|
|
459
|
+
let (sender, mut receiver) = mpsc::unbounded_channel();
|
|
460
|
+
|
|
461
|
+
// Background task to dispatch events
|
|
462
|
+
tokio::spawn(async move {
|
|
463
|
+
while let Some(event) = receiver.recv().await {
|
|
464
|
+
let event_type = format!("{:?}", event).split('{').next().unwrap().trim().to_string();
|
|
465
|
+
let handlers = handlers_clone.lock().unwrap();
|
|
466
|
+
|
|
467
|
+
if let Some(handlers_list) = handlers.get(&event_type) {
|
|
468
|
+
for handler in handlers_list {
|
|
469
|
+
handler.handle(&event);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
Self { handlers, sender }
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
pub fn subscribe(&self, event_type: &str, handler: Arc<dyn EventHandler>) {
|
|
479
|
+
let mut handlers = self.handlers.lock().unwrap();
|
|
480
|
+
handlers
|
|
481
|
+
.entry(event_type.to_string())
|
|
482
|
+
.or_insert_with(Vec::new)
|
|
483
|
+
.push(handler);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
pub fn publish(&self, event: AppEvent) {
|
|
487
|
+
let _ = self.sender.send(event);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Example handlers
|
|
492
|
+
struct LoggingHandler;
|
|
493
|
+
|
|
494
|
+
impl EventHandler for LoggingHandler {
|
|
495
|
+
fn handle(&self, event: &AppEvent) {
|
|
496
|
+
println!("[LOG] Event: {:?}", event);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
struct AnalyticsHandler;
|
|
501
|
+
|
|
502
|
+
impl EventHandler for AnalyticsHandler {
|
|
503
|
+
fn handle(&self, event: &AppEvent) {
|
|
504
|
+
// Send to analytics service
|
|
505
|
+
println!("[ANALYTICS] Tracking: {:?}", event);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Usage
|
|
510
|
+
fn setup_event_bus() -> EventBus {
|
|
511
|
+
let event_bus = EventBus::new();
|
|
512
|
+
|
|
513
|
+
event_bus.subscribe("UserLoggedIn", Arc::new(LoggingHandler));
|
|
514
|
+
event_bus.subscribe("UserLoggedIn", Arc::new(AnalyticsHandler));
|
|
515
|
+
|
|
516
|
+
event_bus
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
#[tauri::command]
|
|
520
|
+
fn login_user(
|
|
521
|
+
event_bus: tauri::State<EventBus>,
|
|
522
|
+
user_id: u64,
|
|
523
|
+
username: String,
|
|
524
|
+
) -> Result<(), String> {
|
|
525
|
+
// Perform login logic...
|
|
526
|
+
|
|
527
|
+
event_bus.publish(AppEvent::UserLoggedIn { user_id, username });
|
|
528
|
+
|
|
529
|
+
Ok(())
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
## Plugin System
|
|
534
|
+
|
|
535
|
+
### Dynamic Plugin Architecture
|
|
536
|
+
|
|
537
|
+
```rust
|
|
538
|
+
use std::collections::HashMap;
|
|
539
|
+
use std::sync::Arc;
|
|
540
|
+
|
|
541
|
+
// Plugin trait
|
|
542
|
+
pub trait Plugin: Send + Sync {
|
|
543
|
+
fn name(&self) -> &str;
|
|
544
|
+
fn version(&self) -> &str;
|
|
545
|
+
fn initialize(&mut self, context: &PluginContext) -> Result<(), String>;
|
|
546
|
+
fn shutdown(&mut self) -> Result<(), String>;
|
|
547
|
+
fn execute(&self, command: &str, args: Vec<String>) -> Result<String, String>;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Plugin context (shared resources)
|
|
551
|
+
pub struct PluginContext {
|
|
552
|
+
pub app_name: String,
|
|
553
|
+
pub config_dir: String,
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Plugin manager
|
|
557
|
+
pub struct PluginManager {
|
|
558
|
+
plugins: HashMap<String, Box<dyn Plugin>>,
|
|
559
|
+
context: Arc<PluginContext>,
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
impl PluginManager {
|
|
563
|
+
pub fn new(context: PluginContext) -> Self {
|
|
564
|
+
Self {
|
|
565
|
+
plugins: HashMap::new(),
|
|
566
|
+
context: Arc::new(context),
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
pub fn register(&mut self, mut plugin: Box<dyn Plugin>) -> Result<(), String> {
|
|
571
|
+
let name = plugin.name().to_string();
|
|
572
|
+
|
|
573
|
+
plugin.initialize(&self.context)?;
|
|
574
|
+
self.plugins.insert(name.clone(), plugin);
|
|
575
|
+
|
|
576
|
+
println!("Plugin registered: {}", name);
|
|
577
|
+
Ok(())
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
pub fn execute(
|
|
581
|
+
&self,
|
|
582
|
+
plugin_name: &str,
|
|
583
|
+
command: &str,
|
|
584
|
+
args: Vec<String>,
|
|
585
|
+
) -> Result<String, String> {
|
|
586
|
+
self.plugins
|
|
587
|
+
.get(plugin_name)
|
|
588
|
+
.ok_or_else(|| format!("Plugin '{}' not found", plugin_name))?
|
|
589
|
+
.execute(command, args)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
pub fn list_plugins(&self) -> Vec<(String, String)> {
|
|
593
|
+
self.plugins
|
|
594
|
+
.values()
|
|
595
|
+
.map(|p| (p.name().to_string(), p.version().to_string()))
|
|
596
|
+
.collect()
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
pub fn shutdown_all(&mut self) -> Result<(), String> {
|
|
600
|
+
for (name, plugin) in self.plugins.iter_mut() {
|
|
601
|
+
plugin.shutdown().map_err(|e| {
|
|
602
|
+
format!("Failed to shutdown plugin '{}': {}", name, e)
|
|
603
|
+
})?;
|
|
604
|
+
}
|
|
605
|
+
Ok(())
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Example plugin
|
|
610
|
+
struct MarkdownPlugin {
|
|
611
|
+
enabled: bool,
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
impl Plugin for MarkdownPlugin {
|
|
615
|
+
fn name(&self) -> &str {
|
|
616
|
+
"markdown"
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
fn version(&self) -> &str {
|
|
620
|
+
"1.0.0"
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
fn initialize(&mut self, _context: &PluginContext) -> Result<(), String> {
|
|
624
|
+
self.enabled = true;
|
|
625
|
+
println!("Markdown plugin initialized");
|
|
626
|
+
Ok(())
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
fn shutdown(&mut self) -> Result<(), String> {
|
|
630
|
+
self.enabled = false;
|
|
631
|
+
println!("Markdown plugin shutdown");
|
|
632
|
+
Ok(())
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
fn execute(&self, command: &str, args: Vec<String>) -> Result<String, String> {
|
|
636
|
+
if !self.enabled {
|
|
637
|
+
return Err("Plugin not enabled".to_string());
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
match command {
|
|
641
|
+
"render" => {
|
|
642
|
+
if args.is_empty() {
|
|
643
|
+
return Err("No markdown text provided".to_string());
|
|
644
|
+
}
|
|
645
|
+
// Simplified markdown rendering
|
|
646
|
+
Ok(format!("<html>{}</html>", args[0]))
|
|
647
|
+
}
|
|
648
|
+
_ => Err(format!("Unknown command: {}", command)),
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Tauri integration
|
|
654
|
+
#[tauri::command]
|
|
655
|
+
fn execute_plugin(
|
|
656
|
+
manager: tauri::State<PluginManager>,
|
|
657
|
+
plugin: String,
|
|
658
|
+
command: String,
|
|
659
|
+
args: Vec<String>,
|
|
660
|
+
) -> Result<String, String> {
|
|
661
|
+
manager.execute(&plugin, &command, args)
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
#[tauri::command]
|
|
665
|
+
fn list_plugins(manager: tauri::State<PluginManager>) -> Vec<(String, String)> {
|
|
666
|
+
manager.list_plugins()
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## Resource Management
|
|
671
|
+
|
|
672
|
+
### Resource Pool Pattern
|
|
673
|
+
|
|
674
|
+
```rust
|
|
675
|
+
use std::sync::{Arc, Mutex};
|
|
676
|
+
use std::collections::VecDeque;
|
|
677
|
+
|
|
678
|
+
pub struct ResourcePool<T> {
|
|
679
|
+
resources: Arc<Mutex<VecDeque<T>>>,
|
|
680
|
+
factory: Arc<dyn Fn() -> T + Send + Sync>,
|
|
681
|
+
max_size: usize,
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
impl<T: Send + 'static> ResourcePool<T> {
|
|
685
|
+
pub fn new<F>(factory: F, max_size: usize) -> Self
|
|
686
|
+
where
|
|
687
|
+
F: Fn() -> T + Send + Sync + 'static,
|
|
688
|
+
{
|
|
689
|
+
Self {
|
|
690
|
+
resources: Arc::new(Mutex::new(VecDeque::new())),
|
|
691
|
+
factory: Arc::new(factory),
|
|
692
|
+
max_size,
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
pub fn acquire(&self) -> PooledResource<T> {
|
|
697
|
+
let resource = {
|
|
698
|
+
let mut pool = self.resources.lock().unwrap();
|
|
699
|
+
pool.pop_front().unwrap_or_else(|| (self.factory)())
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
PooledResource {
|
|
703
|
+
resource: Some(resource),
|
|
704
|
+
pool: self.resources.clone(),
|
|
705
|
+
max_size: self.max_size,
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
pub fn size(&self) -> usize {
|
|
710
|
+
self.resources.lock().unwrap().len()
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
pub struct PooledResource<T> {
|
|
715
|
+
resource: Option<T>,
|
|
716
|
+
pool: Arc<Mutex<VecDeque<T>>>,
|
|
717
|
+
max_size: usize,
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
impl<T> PooledResource<T> {
|
|
721
|
+
pub fn get(&self) -> &T {
|
|
722
|
+
self.resource.as_ref().unwrap()
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
pub fn get_mut(&mut self) -> &mut T {
|
|
726
|
+
self.resource.as_mut().unwrap()
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
impl<T> Drop for PooledResource<T> {
|
|
731
|
+
fn drop(&mut self) {
|
|
732
|
+
if let Some(resource) = self.resource.take() {
|
|
733
|
+
let mut pool = self.pool.lock().unwrap();
|
|
734
|
+
if pool.len() < self.max_size {
|
|
735
|
+
pool.push_back(resource);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Example: Database connection pool
|
|
742
|
+
use sqlx::{SqlitePool, SqliteConnection};
|
|
743
|
+
|
|
744
|
+
pub struct DatabasePool {
|
|
745
|
+
pool: ResourcePool<SqliteConnection>,
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
impl DatabasePool {
|
|
749
|
+
pub async fn new(database_url: &str, max_size: usize) -> Self {
|
|
750
|
+
let url = database_url.to_string();
|
|
751
|
+
Self {
|
|
752
|
+
pool: ResourcePool::new(
|
|
753
|
+
move || {
|
|
754
|
+
// This would need to be async in real implementation
|
|
755
|
+
unimplemented!("Use sqlx::SqlitePool instead")
|
|
756
|
+
},
|
|
757
|
+
max_size,
|
|
758
|
+
),
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
## Error Handling Strategies
|
|
765
|
+
|
|
766
|
+
### Application-Level Error Types
|
|
767
|
+
|
|
768
|
+
```rust
|
|
769
|
+
use thiserror::Error;
|
|
770
|
+
|
|
771
|
+
#[derive(Error, Debug)]
|
|
772
|
+
pub enum AppError {
|
|
773
|
+
#[error("Database error: {0}")]
|
|
774
|
+
Database(#[from] sqlx::Error),
|
|
775
|
+
|
|
776
|
+
#[error("IO error: {0}")]
|
|
777
|
+
Io(#[from] std::io::Error),
|
|
778
|
+
|
|
779
|
+
#[error("Serialization error: {0}")]
|
|
780
|
+
Serialization(#[from] serde_json::Error),
|
|
781
|
+
|
|
782
|
+
#[error("Not found: {0}")]
|
|
783
|
+
NotFound(String),
|
|
784
|
+
|
|
785
|
+
#[error("Validation error: {0}")]
|
|
786
|
+
Validation(String),
|
|
787
|
+
|
|
788
|
+
#[error("Permission denied: {0}")]
|
|
789
|
+
PermissionDenied(String),
|
|
790
|
+
|
|
791
|
+
#[error("Internal error: {0}")]
|
|
792
|
+
Internal(String),
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Convert to Tauri-compatible error
|
|
796
|
+
impl From<AppError> for String {
|
|
797
|
+
fn from(error: AppError) -> Self {
|
|
798
|
+
error.to_string()
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Result type alias
|
|
803
|
+
pub type AppResult<T> = Result<T, AppError>;
|
|
804
|
+
|
|
805
|
+
// Usage in commands
|
|
806
|
+
#[tauri::command]
|
|
807
|
+
async fn save_data(data: String) -> Result<(), String> {
|
|
808
|
+
perform_save(&data)
|
|
809
|
+
.await
|
|
810
|
+
.map_err(|e: AppError| e.to_string())
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async fn perform_save(data: &str) -> AppResult<()> {
|
|
814
|
+
// Validation
|
|
815
|
+
if data.is_empty() {
|
|
816
|
+
return Err(AppError::Validation("Data cannot be empty".to_string()));
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// IO operation
|
|
820
|
+
std::fs::write("data.txt", data)?;
|
|
821
|
+
|
|
822
|
+
Ok(())
|
|
823
|
+
}
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
### Error Recovery Pattern
|
|
827
|
+
|
|
828
|
+
```rust
|
|
829
|
+
use std::time::Duration;
|
|
830
|
+
use tokio::time::sleep;
|
|
831
|
+
|
|
832
|
+
pub struct RetryPolicy {
|
|
833
|
+
max_attempts: u32,
|
|
834
|
+
delay: Duration,
|
|
835
|
+
exponential_backoff: bool,
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
impl RetryPolicy {
|
|
839
|
+
pub fn new(max_attempts: u32, delay: Duration) -> Self {
|
|
840
|
+
Self {
|
|
841
|
+
max_attempts,
|
|
842
|
+
delay,
|
|
843
|
+
exponential_backoff: false,
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
pub fn with_exponential_backoff(mut self) -> Self {
|
|
848
|
+
self.exponential_backoff = true;
|
|
849
|
+
self
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
pub async fn execute<F, T, E>(&self, mut operation: F) -> Result<T, E>
|
|
853
|
+
where
|
|
854
|
+
F: FnMut() -> Result<T, E>,
|
|
855
|
+
E: std::fmt::Display,
|
|
856
|
+
{
|
|
857
|
+
let mut attempt = 0;
|
|
858
|
+
let mut delay = self.delay;
|
|
859
|
+
|
|
860
|
+
loop {
|
|
861
|
+
attempt += 1;
|
|
862
|
+
|
|
863
|
+
match operation() {
|
|
864
|
+
Ok(result) => return Ok(result),
|
|
865
|
+
Err(error) => {
|
|
866
|
+
if attempt >= self.max_attempts {
|
|
867
|
+
println!("Operation failed after {} attempts", attempt);
|
|
868
|
+
return Err(error);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
println!(
|
|
872
|
+
"Attempt {} failed: {}. Retrying in {:?}...",
|
|
873
|
+
attempt, error, delay
|
|
874
|
+
);
|
|
875
|
+
|
|
876
|
+
sleep(delay).await;
|
|
877
|
+
|
|
878
|
+
if self.exponential_backoff {
|
|
879
|
+
delay *= 2;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Usage
|
|
888
|
+
async fn fetch_with_retry(url: &str) -> Result<String, String> {
|
|
889
|
+
let policy = RetryPolicy::new(3, Duration::from_secs(1))
|
|
890
|
+
.with_exponential_backoff();
|
|
891
|
+
|
|
892
|
+
policy
|
|
893
|
+
.execute(|| {
|
|
894
|
+
// Attempt operation
|
|
895
|
+
Ok("data".to_string())
|
|
896
|
+
})
|
|
897
|
+
.await
|
|
898
|
+
}
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
These patterns provide a solid foundation for building maintainable desktop applications. Choose and combine based on application complexity and requirements.
|