@mmerterden/multi-agent-pipeline 20.2.1 → 20.3.1
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.
- package/CHANGELOG.md +919 -1
- package/README.md +104 -81
- package/README.tr.md +103 -62
- package/docs/FIGMA_PIPELINE.md +35 -35
- package/docs/adr/0006-skills-core-external-split.md +1 -1
- package/docs/adr/0007-multi-tool-adapter-framework.md +6 -0
- package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +7 -0
- package/docs/architecture.md +50 -14
- package/docs/best-practices.md +1 -1
- package/docs/ecosystem.md +56 -32
- package/docs/facts.json +10 -10
- package/docs/features.md +97 -5
- package/docs/recovery-guide.md +7 -14
- package/docs/server-readiness.md +31 -24
- package/index.js +1 -1
- package/install/_common.mjs +3 -5
- package/install/_platform-filter.mjs +23 -1
- package/install/_unattended-profile.mjs +321 -75
- package/install/claude.mjs +51 -10
- package/install/codex.mjs +2 -0
- package/install/copilot.mjs +2 -0
- package/install/index.mjs +30 -17
- package/install/templates/claude-hooks.json +16 -5
- package/install/templates/copilot-instructions.md +1 -1
- package/install/templates/multi-agent-autopilot-awake.plist.template +48 -0
- package/install/templates/multi-agent-autopilot.plist.template +12 -5
- package/install/unattended-profile-legacy.json +80 -0
- package/manifest.json +617 -483
- package/package.json +8 -3
- package/pipeline/agents/code-reviewer.md +10 -0
- package/pipeline/agents/plan-critic.md +98 -0
- package/pipeline/agents/security-auditor.md +10 -0
- package/pipeline/agents/task-clarifier.md +10 -0
- package/pipeline/commands/multi-agent/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/analysis/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +12 -1
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/autopilot-off/SKILL.md +36 -6
- package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +77 -12
- package/pipeline/commands/multi-agent/autopilot-status/SKILL.md +29 -8
- package/pipeline/commands/multi-agent/build-optimize/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/channels/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/create-jira/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/design-check/SKILL.md +16 -11
- package/pipeline/commands/multi-agent/diff-explain/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/doctor/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/feedback/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/forget/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/graph/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/help/SKILL.md +37 -39
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/issue/SKILL.md +7 -1
- package/pipeline/commands/multi-agent/jira/SKILL.md +7 -1
- package/pipeline/commands/multi-agent/kill/SKILL.md +9 -3
- package/pipeline/commands/multi-agent/language/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/log/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +5 -0
- package/pipeline/commands/multi-agent/model/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/prune-logs/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/prune-prompts/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/purge/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/refactor/SKILL.md +5 -0
- package/pipeline/commands/multi-agent/research/SKILL.md +49 -0
- package/pipeline/commands/multi-agent/resume/SKILL.md +29 -6
- package/pipeline/commands/multi-agent/review/SKILL.md +28 -9
- package/pipeline/commands/multi-agent/review-analysis/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/review-issue/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/review-jira/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/route-off/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/route-on/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/route-status/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/routines/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/save/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/scaffold/SKILL.md +47 -0
- package/pipeline/commands/multi-agent/scan/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/search/SKILL.md +12 -8
- package/pipeline/commands/multi-agent/security-review/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/serve/SKILL.md +62 -0
- package/pipeline/commands/multi-agent/setup/SKILL.md +27 -36
- package/pipeline/commands/multi-agent/stack/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/status/SKILL.md +10 -9
- package/pipeline/commands/multi-agent/steer/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/sync/SKILL.md +13 -13
- package/pipeline/commands/multi-agent/test/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/test-accessibility/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/test-dark-mode/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/test-dynamic-type/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/test-screenshots/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +4 -0
- package/pipeline/commands/multi-agent/uninstall/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/update/SKILL.md +4 -0
- package/pipeline/contract/CHANGELOG.md +74 -0
- package/pipeline/contract/README.md +126 -0
- package/pipeline/contract/build.mjs +427 -0
- package/pipeline/contract/fixtures/answer-result.json +11 -0
- package/pipeline/contract/fixtures/error-invalid-request.json +8 -0
- package/pipeline/contract/fixtures/error-unauthorized.json +5 -0
- package/pipeline/contract/fixtures/error-unsigned.json +5 -0
- package/pipeline/contract/fixtures/issues-empty.json +18 -0
- package/pipeline/contract/fixtures/launch-plan.json +31 -0
- package/pipeline/contract/fixtures/runs-awaiting-question.json +70 -0
- package/pipeline/contract/fixtures/runs-empty.json +6 -0
- package/pipeline/contract/fixtures/runs-failed.json +84 -0
- package/pipeline/contract/fixtures/runs-old-schema.json +70 -0
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +101 -0
- package/pipeline/contract/fixtures/runs-pr-opened.json +106 -0
- package/pipeline/contract/fixtures/runs-running.json +84 -0
- package/pipeline/contract/fixtures/worktrees-empty.json +4 -0
- package/pipeline/contract/frozen/toolbox.json +107 -0
- package/pipeline/contract/manifest.json +263 -0
- package/pipeline/contract/types/index.d.ts +343 -0
- package/pipeline/lib/_jira-auth.sh +6 -2
- package/pipeline/lib/account-resolver.sh +1 -1
- package/pipeline/lib/autopilot-state.sh +19 -0
- package/pipeline/lib/context-link-extractor.sh +12 -5
- package/pipeline/lib/credential-inventory.sh +12 -5
- package/pipeline/lib/credential-store.sh +116 -185
- package/pipeline/lib/fetch-confluence.sh +44 -3
- package/pipeline/lib/fetch-document.sh +3 -4
- package/pipeline/lib/fetch-fortify.sh +1 -1
- package/pipeline/lib/figma-mcp-refresh.sh +2 -2
- package/pipeline/lib/figma-token.sh +5 -1
- package/pipeline/lib/issue-fetcher.sh +233 -16
- package/pipeline/lib/json-file-lock.mjs +172 -0
- package/pipeline/lib/model-dispatch.sh +21 -12
- package/pipeline/lib/model-rung.sh +6 -1
- package/pipeline/lib/multi-repo-pipeline.sh +1 -1
- package/pipeline/lib/outbound-gate.mjs +46 -16
- package/pipeline/lib/parse-complaints.sh +14 -7
- package/pipeline/lib/plan-todos.sh +3 -3
- package/pipeline/lib/post-pr-review.sh +9 -9
- package/pipeline/lib/pr-request-location.mjs +85 -0
- package/pipeline/lib/regular-file.mjs +153 -0
- package/pipeline/lib/repo-hygiene.sh +17 -0
- package/pipeline/lib/route-state.sh +5 -1
- package/pipeline/lib/run-paths.sh +3 -2
- package/pipeline/lib/stack-detect.sh +19 -1
- package/pipeline/lib/unattended-profile-check.mjs +178 -0
- package/pipeline/lib/unattended-settings-location.mjs +28 -0
- package/pipeline/lib/unattended.mjs +76 -0
- package/pipeline/lib/unattended.sh +32 -0
- package/pipeline/lib/untrusted.mjs +76 -0
- package/pipeline/lib/usage-endpoint.mjs +33 -0
- package/pipeline/lib/user-facing.mjs +82 -0
- package/pipeline/lib/user-facing.sh +58 -0
- package/pipeline/multi-agent-refs/_dev-context.md +1 -1
- package/pipeline/multi-agent-refs/analysis/evidence.md +4 -0
- package/pipeline/multi-agent-refs/analysis/locked.md +2 -2
- package/pipeline/multi-agent-refs/analysis/render.md +4 -3
- package/pipeline/multi-agent-refs/analysis/resolve.md +3 -1
- package/pipeline/multi-agent-refs/analysis/synthesis.md +4 -5
- package/pipeline/multi-agent-refs/analysis-template.md +10 -17
- package/pipeline/multi-agent-refs/channels/jira.md +1 -1
- package/pipeline/multi-agent-refs/component-dispatch.md +3 -3
- package/pipeline/multi-agent-refs/conventions-defaults.md +32 -32
- package/pipeline/multi-agent-refs/cross-cli-contract.md +10 -17
- package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +37 -9
- package/pipeline/multi-agent-refs/features/autopilot-operations.md +277 -0
- package/pipeline/multi-agent-refs/features/constitution.md +196 -0
- package/pipeline/multi-agent-refs/features/doctor.md +6 -3
- package/pipeline/multi-agent-refs/features/external-context-injection.md +6 -1
- package/pipeline/multi-agent-refs/features/jira-context.md +1 -1
- package/pipeline/multi-agent-refs/features/maturity-followup.md +43 -23
- package/pipeline/multi-agent-refs/features/model-fallback.md +9 -0
- package/pipeline/multi-agent-refs/features/phone-api.md +306 -0
- package/pipeline/multi-agent-refs/features/plan-critic.md +159 -0
- package/pipeline/multi-agent-refs/features/research.md +150 -0
- package/pipeline/multi-agent-refs/features/review-decision.md +185 -0
- package/pipeline/multi-agent-refs/features/scaffold.md +160 -0
- package/pipeline/multi-agent-refs/features/security-audit.md +6 -2
- package/pipeline/multi-agent-refs/features/skill-conformance.md +4 -1
- package/pipeline/multi-agent-refs/features/stack-adapters.md +116 -0
- package/pipeline/multi-agent-refs/features/unattended-gates.md +316 -0
- package/pipeline/multi-agent-refs/features/unattended-security.md +731 -0
- package/pipeline/multi-agent-refs/features/url-enrichment.md +2 -2
- package/pipeline/multi-agent-refs/features/usage-reporting.md +127 -35
- package/pipeline/multi-agent-refs/features/verify-by-test.md +1 -1
- package/pipeline/multi-agent-refs/features/visual-evidence.md +7 -7
- package/pipeline/multi-agent-refs/keychain.md +6 -11
- package/pipeline/multi-agent-refs/payload-contracts.md +2 -2
- package/pipeline/multi-agent-refs/phases/log-format.md +2 -3
- package/pipeline/multi-agent-refs/phases/modes.md +10 -12
- package/pipeline/multi-agent-refs/phases/operations.md +11 -5
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +73 -81
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +22 -22
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +42 -66
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +45 -79
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +28 -38
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +27 -25
- package/pipeline/multi-agent-refs/phases.md +1 -1
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/progress-contract.md +13 -16
- package/pipeline/multi-agent-refs/readiness-review.md +1 -1
- package/pipeline/multi-agent-refs/research/engine.md +91 -0
- package/pipeline/multi-agent-refs/rules.md +6 -4
- package/pipeline/multi-agent-refs/setup/repo-discovery.md +2 -2
- package/pipeline/multi-agent-refs/tracker-contract.md +2 -2
- package/pipeline/multi-agent-refs/unattended-contract.md +68 -16
- package/pipeline/rules/figma-pipeline.md +12 -12
- package/pipeline/schemas/agent-state.schema.json +480 -18
- package/pipeline/schemas/analysis-spec.schema.json +4 -4
- package/pipeline/schemas/answer-request.schema.json +24 -0
- package/pipeline/schemas/answer-result.schema.json +28 -0
- package/pipeline/schemas/autopilot-config.schema.json +111 -13
- package/pipeline/schemas/command-parameters.schema.json +99 -0
- package/pipeline/schemas/constitution.schema.json +56 -0
- package/pipeline/schemas/contract-error.schema.json +52 -0
- package/pipeline/schemas/design-check-config.schema.json +5 -1
- package/pipeline/schemas/issues.schema.json +61 -0
- package/pipeline/schemas/launch-plan.schema.json +46 -0
- package/pipeline/schemas/launch-request.schema.json +45 -0
- package/pipeline/schemas/launch.json +61 -0
- package/pipeline/schemas/launch.schema.json +84 -0
- package/pipeline/schemas/phases.json +2 -2
- package/pipeline/schemas/phases.schema.json +68 -0
- package/pipeline/schemas/phone-devices.schema.json +61 -0
- package/pipeline/schemas/phone-signed-request.schema.json +67 -0
- package/pipeline/schemas/plan-critique.schema.json +99 -0
- package/pipeline/schemas/plan-todos.schema.json +7 -7
- package/pipeline/schemas/planning-output.schema.json +5 -0
- package/pipeline/schemas/pr-request.schema.json +46 -0
- package/pipeline/schemas/prefs.schema.json +79 -8
- package/pipeline/schemas/research-output.schema.json +118 -0
- package/pipeline/schemas/review-file-exclusions.schema.json +25 -0
- package/pipeline/schemas/reviewer-output.schema.json +40 -4
- package/pipeline/schemas/run-questions.json +392 -0
- package/pipeline/schemas/run-questions.schema.json +118 -0
- package/pipeline/schemas/runs-index.schema.json +189 -0
- package/pipeline/schemas/scaffold-manifest.schema.json +43 -0
- package/pipeline/schemas/secret-patterns.schema.json +28 -0
- package/pipeline/schemas/stack-adapters.json +527 -0
- package/pipeline/schemas/stack-adapters.schema.json +184 -0
- package/pipeline/schemas/token-budget.json +1 -1
- package/pipeline/schemas/token-budget.schema.json +26 -0
- package/pipeline/schemas/triage-output.schema.json +64 -3
- package/pipeline/schemas/unattended-policy.json +139 -0
- package/pipeline/schemas/unattended-policy.schema.json +73 -0
- package/pipeline/schemas/unattended-profile.json +248 -0
- package/pipeline/schemas/unattended-profile.schema.json +198 -0
- package/pipeline/schemas/worktrees.schema.json +51 -0
- package/pipeline/scripts/README.md +1 -0
- package/pipeline/scripts/_autopilot-config.mjs +130 -0
- package/pipeline/scripts/_autopilot-ops.mjs +567 -0
- package/pipeline/scripts/_autopilot-outcomes.mjs +174 -0
- package/pipeline/scripts/_command-contract.mjs +384 -0
- package/pipeline/scripts/_cost.mjs +40 -0
- package/pipeline/scripts/_notices.mjs +160 -0
- package/pipeline/scripts/_phone-auth.mjs +485 -0
- package/pipeline/scripts/_pre-existing.mjs +294 -0
- package/pipeline/scripts/_redact.mjs +77 -0
- package/pipeline/scripts/_run-paths.mjs +4 -2
- package/pipeline/scripts/_stack-adapter.mjs +678 -0
- package/pipeline/scripts/_stack-routing.mjs +1 -1
- package/pipeline/scripts/agent-guard.py +348 -37
- package/pipeline/scripts/agent-guard.sh +41 -13
- package/pipeline/scripts/analysis-story-tree.mjs +79 -3
- package/pipeline/scripts/answer-question.mjs +181 -0
- package/pipeline/scripts/audit-log-rotate.sh +1 -4
- package/pipeline/scripts/audit-log.sh +4 -4
- package/pipeline/scripts/autopilot-arming.mjs +389 -21
- package/pipeline/scripts/autopilot-awake.mjs +255 -0
- package/pipeline/scripts/autopilot-intake.mjs +137 -36
- package/pipeline/scripts/autopilot-menubar.swift +156 -44
- package/pipeline/scripts/autopilot-publish.mjs +1625 -0
- package/pipeline/scripts/autopilot-runner.mjs +1678 -222
- package/pipeline/scripts/autopilot-status.sh +198 -33
- package/pipeline/scripts/build-lock.sh +120 -0
- package/pipeline/scripts/build-references.mjs +4 -1
- package/pipeline/scripts/build-stack-plugins.mjs +59 -22
- package/pipeline/scripts/capture-flush.sh +1 -1
- package/pipeline/scripts/capture-resume.sh +13 -9
- package/pipeline/scripts/check-derived-drift.mjs +52 -11
- package/pipeline/scripts/commands.mjs +88 -0
- package/pipeline/scripts/constitution.mjs +362 -0
- package/pipeline/scripts/contract-server.mjs +776 -0
- package/pipeline/scripts/cost-analyze.mjs +89 -39
- package/pipeline/scripts/diff-explain.mjs +12 -1
- package/pipeline/scripts/doctor.mjs +77 -28
- package/pipeline/scripts/evidence-gate.mjs +192 -12
- package/pipeline/scripts/feedback-send.mjs +4 -2
- package/pipeline/scripts/gate-ledger.mjs +449 -0
- package/pipeline/scripts/gc-abandoned.sh +132 -13
- package/pipeline/scripts/gen-facts.mjs +31 -15
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
- package/pipeline/scripts/github-ssh-setup.sh +140 -29
- package/pipeline/scripts/graph-mermaid.mjs +4 -1
- package/pipeline/scripts/issues.mjs +236 -0
- package/pipeline/scripts/jira-attach.sh +6 -2
- package/pipeline/scripts/jira-search.sh +4 -3
- package/pipeline/scripts/keychain-save.sh +125 -24
- package/pipeline/scripts/keychain.py +63 -93
- package/pipeline/scripts/launch-request.mjs +747 -0
- package/pipeline/scripts/localize-commands.mjs +4 -10
- package/pipeline/scripts/log-metric.sh +6 -5
- package/pipeline/scripts/maturity-followup.mjs +13 -4
- package/pipeline/scripts/memory-save.sh +25 -0
- package/pipeline/scripts/migrate-prefs.mjs +4 -3
- package/pipeline/scripts/open-questions-gate.mjs +276 -0
- package/pipeline/scripts/phase-tracker.sh +41 -27
- package/pipeline/scripts/phase0-exit-gate.mjs +22 -4
- package/pipeline/scripts/phone-devices.mjs +224 -0
- package/pipeline/scripts/plan-coverage-gate.mjs +200 -66
- package/pipeline/scripts/plan-critique-gate.mjs +591 -0
- package/pipeline/scripts/pr-request.mjs +188 -0
- package/pipeline/scripts/pre-commit-check.sh +115 -4
- package/pipeline/scripts/probe-evidence-capability.sh +44 -5
- package/pipeline/scripts/record-phase.mjs +71 -0
- package/pipeline/scripts/render-agent-log-cost.sh +17 -2
- package/pipeline/scripts/render-cost-summary.sh +1 -1
- package/pipeline/scripts/render-work-summary.sh +1 -1
- package/pipeline/scripts/require-supported-version.sh +4 -1
- package/pipeline/scripts/research-gate.mjs +704 -0
- package/pipeline/scripts/review-decision-gate.mjs +403 -0
- package/pipeline/scripts/routine-registry.mjs +5 -2
- package/pipeline/scripts/runs-index.mjs +135 -27
- package/pipeline/scripts/scaffold-gate.mjs +393 -0
- package/pipeline/scripts/skill-conformance.mjs +25 -8
- package/pipeline/scripts/skill-siblings.mjs +2 -1
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +32 -6
- package/pipeline/scripts/smoke-schema-validation.sh +6 -2
- package/pipeline/scripts/spec-consistency-gate.mjs +469 -0
- package/pipeline/scripts/symbol-existence-gate.mjs +450 -0
- package/pipeline/scripts/test-gap-scan.mjs +40 -2
- package/pipeline/scripts/test-integrity-gate.mjs +20 -4
- package/pipeline/scripts/test-strength.mjs +484 -0
- package/pipeline/scripts/test-summary.mjs +651 -0
- package/pipeline/scripts/triage-memory.mjs +49 -9
- package/pipeline/scripts/unattended_policy.py +2786 -0
- package/pipeline/scripts/uninstall.mjs +10 -10
- package/pipeline/scripts/update-issue-progress.sh +1 -1
- package/pipeline/scripts/usage-identity.mjs +288 -0
- package/pipeline/scripts/usage-register.mjs +187 -67
- package/pipeline/scripts/usage-report.mjs +232 -67
- package/pipeline/scripts/validate-complaint-doc.mjs +28 -10
- package/pipeline/scripts/validate-planning.mjs +6 -0
- package/pipeline/scripts/verify-citations.mjs +151 -38
- package/pipeline/scripts/verify.mjs +58 -18
- package/pipeline/scripts/worktree-prepare.sh +126 -0
- package/pipeline/scripts/worktrees.mjs +124 -0
- package/pipeline/scripts/write-state.mjs +48 -17
- package/pipeline/skills/.skill-manifest.json +222 -226
- package/pipeline/skills/.skills-index.json +77 -88
- package/pipeline/skills/shared/README.md +44 -45
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +3 -2
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +10 -9
- package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +6 -5
- package/pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md +11 -3
- package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +4 -3
- package/pipeline/skills/shared/core/multi-agent-autopilot-off/SKILL.md +32 -6
- package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +66 -8
- package/pipeline/skills/shared/core/multi-agent-autopilot-status/SKILL.md +27 -9
- package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +7 -4
- package/pipeline/skills/shared/core/multi-agent-complaint-analysis/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +7 -5
- package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-doctor/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-feedback/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-forget/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-garbage-collect/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-issue/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-jira/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-kill/SKILL.md +7 -4
- package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-log/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-prune-prompts/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-purge/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-research/SKILL.md +35 -0
- package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +7 -3
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +7 -5
- package/pipeline/skills/shared/core/multi-agent-review-analysis/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-review-issue/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-review-jira/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-route-off/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-routines/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-save/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-scaffold/SKILL.md +30 -0
- package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-search/SKILL.md +4 -3
- package/pipeline/skills/shared/core/multi-agent-security-review/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-serve/SKILL.md +60 -0
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +8 -8
- package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +7 -8
- package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +10 -10
- package/pipeline/skills/shared/core/multi-agent-test/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-test-accessibility/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-test-dark-mode/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-test-dynamic-type/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-test-screenshots/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +2 -1
- package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +3 -2
- package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +2 -1
- package/pipeline/skills/shared/external/NOTICE-avdlee-swiftui-agent-skill.md +46 -0
- package/pipeline/skills/shared/external/NOTICE-dimillian-skills.md +9 -3
- package/pipeline/skills/shared/external/NOTICE-paul-hudson-skills.md +54 -0
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +9 -11
- package/pipeline/skills/shared/external/NOTICE-vibeship-spawner-skills.md +203 -0
- package/pipeline/skills/shared/external/NOTICE-xcode-build-skills.md +10 -3
- package/pipeline/skills/shared/external/accessibility-compliance-accessibility-audit/SKILL.md +131 -25
- package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +4 -3
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +2 -1
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +85 -76
- package/pipeline/skills/shared/external/android-build-quality-gates/SKILL.md +51 -3
- package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +9 -0
- package/pipeline/skills/shared/external/android-datastore/SKILL.md +4 -3
- package/pipeline/skills/shared/external/android-design-tokens-codegen/SKILL.md +4 -3
- package/pipeline/skills/shared/external/android-jetpack-compose-expert/SKILL.md +106 -181
- package/pipeline/skills/shared/external/android-jetpack-compose-expert/references/patterns.md +147 -0
- package/pipeline/skills/shared/external/android-mvi-viewmodel/SKILL.md +45 -4
- package/pipeline/skills/shared/external/android-performance/SKILL.md +27 -2
- package/pipeline/skills/shared/external/android-performance/references/patterns.md +37 -37
- package/pipeline/skills/shared/external/api-patterns/SKILL.md +111 -67
- package/pipeline/skills/shared/external/api-patterns/references/contract-details.md +128 -0
- package/pipeline/skills/shared/external/api-security-best-practices/SKILL.md +136 -186
- package/pipeline/skills/shared/external/api-security-best-practices/references/abuse-controls.md +140 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/identity-and-access.md +207 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/operations.md +66 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/request-handling.md +175 -0
- package/pipeline/skills/shared/external/app-clips/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-intents/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-store-changelog/SKILL.md +4 -3
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +2 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +2 -0
- package/pipeline/skills/shared/external/architecture/SKILL.md +106 -39
- package/pipeline/skills/shared/external/architecture/references/adr-and-review.md +76 -0
- package/pipeline/skills/shared/external/authentication/SKILL.md +2 -0
- package/pipeline/skills/shared/external/avkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/background-processing/SKILL.md +2 -1
- package/pipeline/skills/shared/external/backlog/SKILL.md +3 -2
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +2 -0
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +3 -2
- package/pipeline/skills/shared/external/clean-code/SKILL.md +192 -90
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +2 -1
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/compose-components/SKILL.md +4 -4
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +22 -22
- package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +10 -10
- package/pipeline/skills/shared/external/compose-testing/SKILL.md +13 -6
- package/pipeline/skills/shared/external/compose-testing/references/patterns.md +59 -59
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +2 -0
- package/pipeline/skills/shared/external/context-compression/SKILL.md +118 -250
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +2 -0
- package/pipeline/skills/shared/external/core-data/SKILL.md +2 -0
- package/pipeline/skills/shared/external/core-motion/SKILL.md +2 -0
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +2 -0
- package/pipeline/skills/shared/external/coreml/SKILL.md +2 -1
- package/pipeline/skills/shared/external/council/SKILL.md +2 -1
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/css-modern/SKILL.md +3 -2
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +3 -2
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +2 -0
- package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +130 -20
- package/pipeline/skills/shared/external/debugging-strategies/references/hard-cases.md +54 -0
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +2 -0
- package/pipeline/skills/shared/external/docker-expert/SKILL.md +137 -381
- package/pipeline/skills/shared/external/docker-expert/references/patterns.md +98 -0
- package/pipeline/skills/shared/external/energykit/SKILL.md +2 -1
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +3 -1
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/fastapi-pro/SKILL.md +116 -185
- package/pipeline/skills/shared/external/fastapi-pro/references/app-structure.md +235 -0
- package/pipeline/skills/shared/external/fastapi-pro/references/testing-and-deployment.md +69 -0
- package/pipeline/skills/shared/external/firebase/SKILL.md +4 -3
- package/pipeline/skills/shared/external/github-actions-templates/SKILL.md +151 -293
- package/pipeline/skills/shared/external/github-actions-templates/references/patterns.md +96 -0
- package/pipeline/skills/shared/external/healthkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/hig-components-content/SKILL.md +69 -72
- package/pipeline/skills/shared/external/hig-components-content/references/content-views.md +111 -0
- package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +77 -86
- package/pipeline/skills/shared/external/hig-components-layout/references/containers.md +98 -0
- package/pipeline/skills/shared/external/hig-components-status/SKILL.md +150 -78
- package/pipeline/skills/shared/external/hig-components-system/SKILL.md +76 -97
- package/pipeline/skills/shared/external/hig-components-system/references/surfaces.md +98 -0
- package/pipeline/skills/shared/external/hig-foundations/SKILL.md +48 -76
- package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +132 -0
- package/pipeline/skills/shared/external/hig-inputs/SKILL.md +147 -106
- package/pipeline/skills/shared/external/hig-patterns/SKILL.md +61 -77
- package/pipeline/skills/shared/external/hig-patterns/references/patterns.md +92 -0
- package/pipeline/skills/shared/external/hig-platforms/SKILL.md +147 -77
- package/pipeline/skills/shared/external/hig-technologies/SKILL.md +61 -121
- package/pipeline/skills/shared/external/hig-technologies/references/technologies.md +108 -0
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +2 -0
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +3 -2
- package/pipeline/skills/shared/external/humanizer/SKILL.md +2 -1
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +2 -0
- package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +2 -1
- package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +14 -2
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
- package/pipeline/skills/shared/external/ios-debugger-agent/SKILL.md +4 -3
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +8 -6
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +11 -11
- package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +2 -1
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +2 -0
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +2 -0
- package/pipeline/skills/shared/external/kotlin-coroutines-expert/SKILL.md +102 -193
- package/pipeline/skills/shared/external/kotlin-coroutines-expert/references/patterns.md +103 -0
- package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -2
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +7 -9
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +2 -2
- package/pipeline/skills/shared/external/macos-menubar-tuist-app/SKILL.md +4 -3
- package/pipeline/skills/shared/external/macos-spm-app-packaging/SKILL.md +5 -3
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +2 -0
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +2 -0
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/monorepo-architect/SKILL.md +149 -46
- package/pipeline/skills/shared/external/monorepo-architect/references/patterns.md +70 -0
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +2 -0
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/natural-language/SKILL.md +2 -0
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +3 -2
- package/pipeline/skills/shared/external/nodejs-backend-patterns/SKILL.md +91 -21
- package/pipeline/skills/shared/external/nodejs-backend-patterns/references/runtime-patterns.md +247 -0
- package/pipeline/skills/shared/external/nodejs-backend-patterns/references/testing-and-frameworks.md +57 -0
- package/pipeline/skills/shared/external/observability-engineer/SKILL.md +155 -232
- package/pipeline/skills/shared/external/observability-engineer/references/patterns.md +75 -0
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +2 -0
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +18 -18
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +2 -1
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +2 -0
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +2 -1
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +2 -0
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +3 -2
- package/pipeline/skills/shared/external/react-best-practices/SKILL.md +3 -2
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +2 -2
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +3 -2
- package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +7 -7
- package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +69 -69
- package/pipeline/skills/shared/external/room-database/references/patterns.md +126 -126
- package/pipeline/skills/shared/external/search-first/SKILL.md +2 -1
- package/pipeline/skills/shared/external/security-review/SKILL.md +4 -3
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +2 -2
- package/pipeline/skills/shared/external/signal-community/SKILL.md +1 -1
- package/pipeline/skills/shared/external/skill-creator/SKILL.md +2 -1
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +2 -1
- package/pipeline/skills/shared/external/spm-build-analysis/SKILL.md +2 -0
- package/pipeline/skills/shared/external/storekit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swift-concurrency-expert/SKILL.md +5 -4
- package/pipeline/skills/shared/external/swift-concurrency-pro/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swift-security/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swift-testing-pro/references/new-features.md +10 -0
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftdata-pro/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +3 -1
- package/pipeline/skills/shared/external/swiftui-performance-audit/SKILL.md +5 -4
- package/pipeline/skills/shared/external/swiftui-pro/SKILL.md +2 -1
- package/pipeline/skills/shared/external/swiftui-ui-patterns/SKILL.md +5 -4
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +2 -0
- package/pipeline/skills/shared/external/swiftui-view-refactor/SKILL.md +4 -3
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +3 -2
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +3 -2
- package/pipeline/skills/shared/external/tipkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +3 -2
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -0
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +3 -2
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +3 -2
- package/pipeline/skills/shared/external/web-performance/SKILL.md +3 -2
- package/pipeline/skills/shared/external/web-testing/SKILL.md +3 -2
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +2 -0
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +1 -1
- package/pipeline/skills/shared/external/xcode-build-benchmark/SKILL.md +2 -0
- package/pipeline/skills/shared/external/xcode-build-fixer/SKILL.md +2 -0
- package/pipeline/skills/shared/external/xcode-build-orchestrator/SKILL.md +2 -0
- package/pipeline/skills/shared/external/xcode-compilation-analyzer/SKILL.md +2 -0
- package/pipeline/skills/shared/external/xcode-project-analyzer/SKILL.md +2 -0
- package/pipeline/skills/skills-index.md +40 -41
- package/docs/token-budget-history.md +0 -24
- package/pipeline/skills/shared/external/agentflow/SKILL.md +0 -199
- package/pipeline/skills/shared/external/android-ui-verification/SKILL.md +0 -66
- package/pipeline/skills/shared/external/api-security-best-practices/references/auth.md +0 -299
- package/pipeline/skills/shared/external/api-security-best-practices/references/input-validation.md +0 -255
- package/pipeline/skills/shared/external/api-security-best-practices/references/rate-limiting.md +0 -167
- package/pipeline/skills/shared/external/closed-loop-delivery/SKILL.md +0 -116
- package/pipeline/skills/shared/external/ios-developer/SKILL.md +0 -216
|
@@ -1,215 +1,140 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: android-jetpack-compose-expert
|
|
3
|
-
description: "
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
description: "Compose runtime expertise: state ownership, recomposition and stability, side-effect APIs, lazy lists, modifier order, Modifier.Node. Use when writing or reviewing composables, or when a screen recomposes too often or loses state."
|
|
4
|
+
metadata:
|
|
5
|
+
risk: safe
|
|
6
|
+
source: multi-agent-pipeline
|
|
7
|
+
date_added: "2026-09-25"
|
|
8
|
+
not-for: compose-components, android-performance
|
|
7
9
|
---
|
|
8
10
|
|
|
9
|
-
#
|
|
11
|
+
# Jetpack Compose Runtime Expertise
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Compose calls your functions again whenever state they read changes. Almost
|
|
14
|
+
every Compose bug is a disagreement between what the author thought would be
|
|
15
|
+
re-run and what the runtime actually re-runs. This skill is about that model.
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
Sibling skills cover the rest: Material components and theming in
|
|
18
|
+
`compose-components`, routes and back stack in `compose-navigation`, UI and
|
|
19
|
+
ViewModel tests in `compose-testing`, layering and DI in `android-architecture`,
|
|
20
|
+
the MVI base class in `android-mvi-viewmodel`, and profiling, Baseline Profiles
|
|
21
|
+
and R8 in `android-performance`.
|
|
14
22
|
|
|
15
|
-
##
|
|
23
|
+
## The three phases
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- Use when optimizing Compose performance (recomposition counts, stability).
|
|
21
|
-
- Use when setting up Navigation with type safety.
|
|
25
|
+
A frame runs composition (what to show), layout (where and how big), then
|
|
26
|
+
drawing. A state read is tracked in the phase where it happens, and only that
|
|
27
|
+
phase and the ones after it re-run when the value changes.
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
| Read happens in | Re-runs on change | Typical API |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| Composable body | composition, layout, draw | `Text(text = state.title)` |
|
|
32
|
+
| Layout lambda | layout, draw | `Modifier.offset { IntOffset(x, 0) }` |
|
|
33
|
+
| Draw lambda | draw only | `Modifier.drawBehind { drawRect(color) }` |
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Ensure your `libs.versions.toml` includes the necessary Compose BOM and libraries.
|
|
28
|
-
|
|
29
|
-
```kotlin
|
|
30
|
-
[versions]
|
|
31
|
-
composeBom = "2024.02.01"
|
|
32
|
-
activityCompose = "1.8.2"
|
|
33
|
-
|
|
34
|
-
[libraries]
|
|
35
|
-
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
|
36
|
-
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
|
37
|
-
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
|
38
|
-
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
|
39
|
-
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
|
40
|
-
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### 2. State Management Pattern (MVI/MVVM)
|
|
44
|
-
|
|
45
|
-
Use `ViewModel` with `StateFlow` to expose UI state. Avoid exposing `MutableStateFlow`.
|
|
35
|
+
Move fast-changing reads (scroll offsets, animation values) into the lambda
|
|
36
|
+
variants so they skip composition entirely:
|
|
46
37
|
|
|
47
38
|
```kotlin
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
val isLoading: Boolean = false,
|
|
51
|
-
val user: User? = null,
|
|
52
|
-
val error: String? = null
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
// ViewModel
|
|
56
|
-
class UserViewModel @Inject constructor(
|
|
57
|
-
private val userRepository: UserRepository
|
|
58
|
-
) : ViewModel() {
|
|
59
|
-
|
|
60
|
-
private val _uiState = MutableStateFlow(UserUiState())
|
|
61
|
-
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
|
|
62
|
-
|
|
63
|
-
fun loadUser() {
|
|
64
|
-
viewModelScope.launch {
|
|
65
|
-
_uiState.update { it.copy(isLoading = true) }
|
|
66
|
-
try {
|
|
67
|
-
val user = userRepository.getUser()
|
|
68
|
-
_uiState.update { it.copy(user = user, isLoading = false) }
|
|
69
|
-
} catch (e: Exception) {
|
|
70
|
-
_uiState.update { it.copy(error = e.message, isLoading = false) }
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Reduce state with `.update { copy(...) }` (atomic read-modify-write; safe under concurrent emissions) rather than assigning `_uiState.value = ...`.
|
|
39
|
+
// Recomposes the whole header on every scroll pixel
|
|
40
|
+
Header(modifier = Modifier.alpha(1f - listState.firstVisibleItemScrollOffset / 300f))
|
|
78
41
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
val uiState: StateFlow<UserUiState> = _uiState
|
|
85
|
-
.onStart { loadInitialData() }
|
|
86
|
-
.stateIn(
|
|
87
|
-
scope = viewModelScope,
|
|
88
|
-
started = SharingStarted.WhileSubscribed(5_000),
|
|
89
|
-
initialValue = UserUiState()
|
|
90
|
-
)
|
|
42
|
+
// Reads the value in the draw phase only
|
|
43
|
+
Header(modifier = Modifier.graphicsLayer {
|
|
44
|
+
alpha = 1f - listState.firstVisibleItemScrollOffset / 300f
|
|
45
|
+
})
|
|
91
46
|
```
|
|
92
47
|
|
|
93
|
-
|
|
48
|
+
## Derived values
|
|
94
49
|
|
|
95
|
-
|
|
50
|
+
Computing something from state inside the body is fine when it is cheap. Two
|
|
51
|
+
cases need care:
|
|
96
52
|
|
|
97
|
-
|
|
53
|
+
- **Expensive work** keyed on inputs: `val sorted = remember(products, sortKey) { products.sortedWith(sortKey.comparator) }`.
|
|
54
|
+
- **A value that changes less often than its source**: wrap it in
|
|
55
|
+
`derivedStateOf` so readers recompose only when the result flips.
|
|
98
56
|
|
|
99
57
|
```kotlin
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
viewModel: UserViewModel = hiltViewModel()
|
|
103
|
-
) {
|
|
104
|
-
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
|
105
|
-
|
|
106
|
-
UserContent(
|
|
107
|
-
uiState = uiState,
|
|
108
|
-
onRetry = viewModel::loadUser
|
|
109
|
-
)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
@Composable
|
|
113
|
-
fun UserContent(
|
|
114
|
-
uiState: UserUiState,
|
|
115
|
-
onRetry: () -> Unit
|
|
116
|
-
) {
|
|
117
|
-
Scaffold { padding ->
|
|
118
|
-
Box(modifier = Modifier.padding(padding)) {
|
|
119
|
-
when {
|
|
120
|
-
uiState.isLoading -> CircularProgressIndicator()
|
|
121
|
-
uiState.error != null -> ErrorView(uiState.error, onRetry)
|
|
122
|
-
uiState.user != null -> UserProfile(uiState.user)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
58
|
+
val showScrollToTop by remember {
|
|
59
|
+
derivedStateOf { listState.firstVisibleItemIndex > 0 }
|
|
126
60
|
}
|
|
127
61
|
```
|
|
128
62
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
### Example 1: Type-Safe Navigation
|
|
63
|
+
Do not use `derivedStateOf` to combine two ordinary parameters; it only helps
|
|
64
|
+
when the source changes more often than the output.
|
|
132
65
|
|
|
133
|
-
|
|
66
|
+
## Stability and skipping
|
|
134
67
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
@Serializable
|
|
141
|
-
data class Profile(val userId: String)
|
|
142
|
-
|
|
143
|
-
// Setup NavHost
|
|
144
|
-
@Composable
|
|
145
|
-
fun AppNavHost(navController: NavHostController) {
|
|
146
|
-
NavHost(navController, startDestination = Home) {
|
|
147
|
-
composable<Home> {
|
|
148
|
-
HomeScreen(onNavigateToProfile = { id ->
|
|
149
|
-
navController.navigate(Profile(userId = id))
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
composable<Profile> { backStackEntry ->
|
|
153
|
-
val profile: Profile = backStackEntry.toRoute()
|
|
154
|
-
ProfileScreen(userId = profile.userId)
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
```
|
|
68
|
+
With strong skipping mode (default since Kotlin 2.0.20 with the Compose compiler
|
|
69
|
+
Gradle plugin), a restartable composable skips when every argument is equal to
|
|
70
|
+
the previous one: `equals` for stable types, reference identity for unstable
|
|
71
|
+
ones. Lambdas are remembered automatically. So the practical rules are:
|
|
159
72
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
73
|
+
- Pass immutable data. `data class Product(val id: String, val name: String)`
|
|
74
|
+
with `val` properties of stable types is stable.
|
|
75
|
+
- `List`, `Set` and `Map` are interfaces and count as unstable. Use
|
|
76
|
+
`kotlinx.collections.immutable` (`ImmutableList`) or list them in a
|
|
77
|
+
compiler stability configuration file if they are never mutated.
|
|
78
|
+
- Classes from modules without the Compose compiler (domain modules) are
|
|
79
|
+
unstable unless listed in the stability config or wrapped in a UI model.
|
|
80
|
+
- Do not allocate a new list or object in the parent body just to pass it down;
|
|
81
|
+
identity changes and the child cannot skip.
|
|
169
82
|
|
|
170
|
-
|
|
171
|
-
|
|
83
|
+
Mark a class `@Immutable` only when it truly never changes after construction,
|
|
84
|
+
and `@Stable` when changes are notified through snapshot state. A wrong
|
|
85
|
+
annotation causes stale UI, which is worse than an extra recomposition.
|
|
172
86
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
```
|
|
87
|
+
Measure before annotating: the Layout Inspector recomposition counts and the
|
|
88
|
+
compiler reports (`reportsDestination`) show what is skipping. Tooling detail
|
|
89
|
+
lives in `android-performance`.
|
|
179
90
|
|
|
180
|
-
|
|
91
|
+
## Lazy lists
|
|
181
92
|
|
|
182
93
|
```kotlin
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
is UserEvent.ShowMessage -> snackbarHostState.showSnackbar(event.text)
|
|
191
|
-
is UserEvent.NavigateToProfile -> onNavigateToProfile(event.userId)
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
94
|
+
LazyColumn(state = listState) {
|
|
95
|
+
items(
|
|
96
|
+
items = state.notes,
|
|
97
|
+
key = { note -> note.id },
|
|
98
|
+
contentType = { note -> if (note.isPinned) "pinned" else "regular" },
|
|
99
|
+
) { note ->
|
|
100
|
+
NoteRow(note = note, onClick = { onNoteClick(note.id) }, modifier = Modifier.animateItem())
|
|
195
101
|
}
|
|
196
|
-
// ...
|
|
197
102
|
}
|
|
198
103
|
```
|
|
199
104
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
105
|
+
- `key` keeps item state and scroll position attached to the right row when
|
|
106
|
+
the list reorders or items are inserted. It must be stable and unique, and
|
|
107
|
+
`Bundle`-saveable (String, Int, Long).
|
|
108
|
+
- `contentType` lets the list reuse compositions between rows of the same shape.
|
|
109
|
+
- `Modifier.animateItem()` animates insertion, removal and moves; it needs keys.
|
|
110
|
+
- Never nest a vertically scrolling lazy list inside `verticalScroll`; give the
|
|
111
|
+
inner list a fixed height or flatten it into the outer list with `item {}`
|
|
112
|
+
blocks.
|
|
113
|
+
|
|
114
|
+
## CompositionLocal
|
|
115
|
+
|
|
116
|
+
Use a `CompositionLocal` for values that are ambient to a subtree and rarely
|
|
117
|
+
change: theme tokens, a formatter, analytics sink. Do not use it to smuggle
|
|
118
|
+
screen data or ViewModels past parameter lists; that hides dependencies and
|
|
119
|
+
makes previews fail at runtime.
|
|
120
|
+
|
|
121
|
+
- `staticCompositionLocalOf` for values that almost never change (whole subtree
|
|
122
|
+
recomposes on change, reads are cheap).
|
|
123
|
+
- `compositionLocalOf` when the value changes and only readers should recompose.
|
|
124
|
+
- Always provide a sensible default or throw with a message naming the provider.
|
|
125
|
+
|
|
126
|
+
## Review checklist
|
|
127
|
+
|
|
128
|
+
- Screen composable receives state and lambdas, not a ViewModel.
|
|
129
|
+
- State collected with `collectAsStateWithLifecycle`.
|
|
130
|
+
- No side effect, logging or allocation-heavy work in a composable body.
|
|
131
|
+
- Every effect has keys that match its real dependencies.
|
|
132
|
+
- Fast-changing values read in layout or draw lambdas.
|
|
133
|
+
- Lazy list items have stable keys; no nested same-direction scrolling.
|
|
134
|
+
- Parameters are stable or the instability is measured and accepted.
|
|
135
|
+
- Custom modifiers use `Modifier.Node`; `modifier` parameter applied once.
|
|
136
|
+
- User-facing strings via `stringResource`, sizes via theme or dimension tokens.
|
|
137
|
+
|
|
138
|
+
## References
|
|
139
|
+
|
|
140
|
+
- `references/patterns.md`: state holders and hoisting, the side-effect API table with key rules, and modifier order plus Modifier.Node. Load it when the task reaches those topics.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Compose runtime patterns
|
|
2
|
+
|
|
3
|
+
Loaded on demand from `../SKILL.md`.
|
|
4
|
+
|
|
5
|
+
## Who owns a piece of state
|
|
6
|
+
|
|
7
|
+
Decide ownership before writing the composable. The rule of thumb: state lives
|
|
8
|
+
in the lowest common parent of everything that reads or writes it, and no lower.
|
|
9
|
+
|
|
10
|
+
| Kind of state | Owner | Survives |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| Screen data from a repository | ViewModel, exposed as `StateFlow` | configuration change |
|
|
13
|
+
| User input the ViewModel must validate | ViewModel | configuration change |
|
|
14
|
+
| Pure UI element state (expanded, scroll) | composable via `remember` / `rememberSaveable` | depends on the API |
|
|
15
|
+
| Shared by a group of UI elements | a plain state holder class, remembered | same as `remember` |
|
|
16
|
+
|
|
17
|
+
Collect ViewModel state with the lifecycle-aware collector so collection stops
|
|
18
|
+
when the screen is not visible:
|
|
19
|
+
|
|
20
|
+
```kotlin
|
|
21
|
+
@Composable
|
|
22
|
+
fun OrderListRoute(viewModel: OrderListViewModel = hiltViewModel()) {
|
|
23
|
+
val state by viewModel.state.collectAsStateWithLifecycle()
|
|
24
|
+
OrderListScreen(
|
|
25
|
+
state = state,
|
|
26
|
+
onRefresh = viewModel::refresh,
|
|
27
|
+
onOrderClick = viewModel::openOrder,
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`OrderListScreen` takes only values and lambdas. That split keeps the screen
|
|
33
|
+
previewable and testable without a ViewModel, and it stops the ViewModel from
|
|
34
|
+
leaking into child composables.
|
|
35
|
+
|
|
36
|
+
### remember versus rememberSaveable
|
|
37
|
+
|
|
38
|
+
- `remember` keeps a value across recompositions; it is lost when the
|
|
39
|
+
composable leaves the composition or the activity is recreated.
|
|
40
|
+
- `rememberSaveable` also writes to the saved-instance bundle, so it survives
|
|
41
|
+
rotation and process death. Use it for text field contents, selected tab,
|
|
42
|
+
expanded flags. Custom types need a `Saver` or `@Parcelize`.
|
|
43
|
+
- Pass keys when the remembered value depends on an input:
|
|
44
|
+
`remember(noteId) { NoteDraft(noteId) }` rebuilds when `noteId` changes.
|
|
45
|
+
|
|
46
|
+
### A plain state holder
|
|
47
|
+
|
|
48
|
+
When several related UI values move together, group them in a class rather
|
|
49
|
+
than scattering `mutableStateOf` calls:
|
|
50
|
+
|
|
51
|
+
```kotlin
|
|
52
|
+
@Stable
|
|
53
|
+
class FilterBarState(initial: Set<Category>) {
|
|
54
|
+
var selected by mutableStateOf(initial)
|
|
55
|
+
private set
|
|
56
|
+
var isExpanded by mutableStateOf(false)
|
|
57
|
+
private set
|
|
58
|
+
|
|
59
|
+
fun toggle(category: Category) {
|
|
60
|
+
selected = if (category in selected) selected - category else selected + category
|
|
61
|
+
}
|
|
62
|
+
fun expand(value: Boolean) { isExpanded = value }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Composable
|
|
66
|
+
fun rememberFilterBarState(initial: Set<Category> = emptySet()) =
|
|
67
|
+
remember { FilterBarState(initial) }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Side effects
|
|
71
|
+
|
|
72
|
+
The body of a composable must be free of side effects because it can run many
|
|
73
|
+
times, out of order, or be discarded. Every effect goes through one of these:
|
|
74
|
+
|
|
75
|
+
| Need | API | Restarts when |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| Run a suspend block tied to the composable | `LaunchedEffect(key)` | key changes |
|
|
78
|
+
| Register and unregister a listener | `DisposableEffect(key)` with `onDispose` | key changes |
|
|
79
|
+
| Launch from a click handler | `rememberCoroutineScope()` | never; scope dies with the composable |
|
|
80
|
+
| Publish Compose state to non-Compose code after every successful recomposition | `SideEffect` | every recomposition |
|
|
81
|
+
| Turn a callback-based source into state | `produceState` | key changes |
|
|
82
|
+
| Observe state as a Flow inside an effect | `snapshotFlow { ... }` | emits on change |
|
|
83
|
+
| Use the latest lambda inside a long effect without restarting it | `rememberUpdatedState(lambda)` | never |
|
|
84
|
+
|
|
85
|
+
```kotlin
|
|
86
|
+
@Composable
|
|
87
|
+
fun AutoSaveDraft(draft: String, onSave: suspend (String) -> Unit) {
|
|
88
|
+
val currentDraft by rememberUpdatedState(draft)
|
|
89
|
+
val latestOnSave by rememberUpdatedState(onSave)
|
|
90
|
+
LaunchedEffect(Unit) {
|
|
91
|
+
snapshotFlow { currentDraft }
|
|
92
|
+
.debounce(500)
|
|
93
|
+
.distinctUntilChanged()
|
|
94
|
+
.collect { latestOnSave(it) }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`snapshotFlow` only sees changes to snapshot state it reads, so the plain
|
|
100
|
+
`draft` parameter is wrapped in `rememberUpdatedState` first; reading the bare
|
|
101
|
+
parameter would capture its first value and never emit again.
|
|
102
|
+
|
|
103
|
+
Choosing keys: use the identity the effect depends on (`LaunchedEffect(orderId)`),
|
|
104
|
+
never `true` to mean "once" if the dependency can change, and never a fresh
|
|
105
|
+
object allocated in the body, which restarts the effect every recomposition.
|
|
106
|
+
|
|
107
|
+
One-shot events from a ViewModel (show a snackbar, navigate) are collected in a
|
|
108
|
+
`LaunchedEffect` with `repeatOnLifecycle` semantics, or modelled as state the UI
|
|
109
|
+
consumes and acknowledges. `android-mvi-viewmodel` covers the channel approach.
|
|
110
|
+
|
|
111
|
+
## Modifiers
|
|
112
|
+
|
|
113
|
+
Order matters because each modifier wraps the ones after it.
|
|
114
|
+
|
|
115
|
+
```kotlin
|
|
116
|
+
Modifier
|
|
117
|
+
.padding(16.dp) // outer spacing, not clickable
|
|
118
|
+
.clip(RoundedCornerShape(12.dp))
|
|
119
|
+
.background(MaterialTheme.colorScheme.surfaceVariant)
|
|
120
|
+
.clickable(onClick = onClick) // ripple is clipped to the shape
|
|
121
|
+
.padding(12.dp) // inner spacing, inside the touch target
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- Every public composable takes `modifier: Modifier = Modifier` as its first
|
|
125
|
+
optional parameter and applies it to the root, once.
|
|
126
|
+
- Write custom modifiers with `Modifier.Node` and a `ModifierNodeElement`;
|
|
127
|
+
`Modifier.composed` is discouraged because it allocates per call site and
|
|
128
|
+
cannot skip.
|
|
129
|
+
|
|
130
|
+
```kotlin
|
|
131
|
+
private class DimWhenNode(var dimmed: Boolean) : Modifier.Node(), DrawModifierNode {
|
|
132
|
+
override fun ContentDrawScope.draw() {
|
|
133
|
+
drawContent()
|
|
134
|
+
if (dimmed) drawRect(Color.Black.copy(alpha = 0.4f))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private data class DimWhenElement(val dimmed: Boolean) : ModifierNodeElement<DimWhenNode>() {
|
|
139
|
+
override fun create() = DimWhenNode(dimmed)
|
|
140
|
+
override fun update(node: DimWhenNode) {
|
|
141
|
+
node.dimmed = dimmed
|
|
142
|
+
node.invalidateDraw()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fun Modifier.dimWhen(dimmed: Boolean) = this then DimWhenElement(dimmed)
|
|
147
|
+
```
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: android-mvi-viewmodel
|
|
3
|
-
description: "A reusable generic BaseViewModel
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
description: "A reusable generic BaseViewModel typed on State, Intent and Event for MVI Compose apps: StateFlow state, one-shot events over a rendezvous Channel, a single intent dispatch, and a use-case executor with shared loading and error routing. Use when building or reviewing an MVI ViewModel base, intent-to-state reduction, or one-shot navigation and snackbar events."
|
|
4
|
+
metadata:
|
|
5
|
+
risk: safe
|
|
6
|
+
source: multi-agent-pipeline
|
|
7
|
+
date_added: "2026-09-21"
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
# Android MVI BaseViewModel
|
|
@@ -25,6 +26,7 @@ complete implementation to adapt; each heading there mirrors a section below.
|
|
|
25
26
|
- [State as StateFlow](#state-as-stateflow)
|
|
26
27
|
- [One-shot events over a rendezvous channel](#one-shot-events-over-a-rendezvous-channel)
|
|
27
28
|
- [Single intent entry point](#single-intent-entry-point)
|
|
29
|
+
- [Same-frame input intents](#same-frame-input-intents)
|
|
28
30
|
- [Use-case executor](#use-case-executor)
|
|
29
31
|
- [Reference-counted loading](#reference-counted-loading)
|
|
30
32
|
- [Active job registry](#active-job-registry)
|
|
@@ -119,6 +121,38 @@ override fun handleIntent(intent: SearchIntent) = when (intent) {
|
|
|
119
121
|
}
|
|
120
122
|
```
|
|
121
123
|
|
|
124
|
+
## Same-frame input intents
|
|
125
|
+
|
|
126
|
+
A base that launches every intent in a coroutine (so handlers can suspend) must
|
|
127
|
+
not send all of them to `Dispatchers.Default`. The hop costs a frame, and for an
|
|
128
|
+
intent that edits input state (a `TextField` value, a toggle, a radio selection)
|
|
129
|
+
that frame is visible: the cursor jumps or the text flickers, because Compose
|
|
130
|
+
renders the old value once more before the new one arrives.
|
|
131
|
+
|
|
132
|
+
Give the intent contract a flag, and run flagged intents on the ViewModel's own
|
|
133
|
+
context, which is `Dispatchers.Main.immediate` for `viewModelScope`:
|
|
134
|
+
|
|
135
|
+
```kotlin
|
|
136
|
+
interface UiIntent {
|
|
137
|
+
val immediateDispatch: Boolean get() = false
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fun processIntent(intent: Intent) {
|
|
141
|
+
val context = if (intent.immediateDispatch) EmptyCoroutineContext else Dispatchers.Default
|
|
142
|
+
viewModelScope.launch(context) { handleIntent(intent) }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
data class QueryChanged(val text: String) : SearchIntent {
|
|
146
|
+
override val immediateDispatch = true
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The flag only decides where the state write happens. A flagged handler that
|
|
151
|
+
needs I/O still moves that work off the main thread with `withContext`. The
|
|
152
|
+
same contract can carry per-intent loading flags (show a blocking loader,
|
|
153
|
+
allow tap-to-cancel), so the executor below reads them from the intent instead
|
|
154
|
+
of taking extra arguments.
|
|
155
|
+
|
|
122
156
|
## Use-case executor
|
|
123
157
|
|
|
124
158
|
A single protected helper wraps every suspending call so error handling is not
|
|
@@ -147,6 +181,12 @@ protected fun <T> execute(
|
|
|
147
181
|
}
|
|
148
182
|
```
|
|
149
183
|
|
|
184
|
+
To stop a use case from being invoked outside this helper, which would bypass
|
|
185
|
+
loading and error routing, declare its `invoke` with a context parameter the
|
|
186
|
+
base class provides (`context(executor: UseCaseExecutor) operator fun invoke(...)`,
|
|
187
|
+
Kotlin 2.2+ with `-Xcontext-parameters`). Only code inside the executor scope
|
|
188
|
+
can call it. Tests call it inside a faked executor with `with(executor) { ... }`.
|
|
189
|
+
|
|
150
190
|
Error routing has two tiers: a per-call `onError` for screen-specific handling,
|
|
151
191
|
falling back to a global `routeError` that maps the throwable to an `Event`
|
|
152
192
|
(snackbar) or an error field in state. Define `routeError` once in the base or
|
|
@@ -242,6 +282,7 @@ full body is in [references/patterns.md](references/patterns.md).
|
|
|
242
282
|
them.
|
|
243
283
|
- **Don't** pass the ViewModel to child composables; pass state + lambdas.
|
|
244
284
|
- **Don't** toggle a boolean for loading when calls overlap; count references.
|
|
285
|
+
- **Don't** dispatch input-editing intents through `Dispatchers.Default`; flag them for same-frame dispatch.
|
|
245
286
|
|
|
246
287
|
## Related skills
|
|
247
288
|
|
|
@@ -17,6 +17,7 @@ or review that area; the guidance here is enough to decide what to do.
|
|
|
17
17
|
|
|
18
18
|
- [Baseline Profiles](#baseline-profiles)
|
|
19
19
|
- [Compose Stability](#compose-stability)
|
|
20
|
+
- [Deferred State Reads](#deferred-state-reads)
|
|
20
21
|
- [Recomposition Debugging](#recomposition-debugging)
|
|
21
22
|
- [Lazy Layout Performance](#lazy-layout-performance)
|
|
22
23
|
- [R8 Optimization](#r8-optimization)
|
|
@@ -55,6 +56,7 @@ For this to work, parameters must be **stable** (immutable or observable).
|
|
|
55
56
|
| `data class` with `List<T>` field | No | `List` is an interface; could be mutable |
|
|
56
57
|
| `class` (non-data) | No | Compose cannot infer stability |
|
|
57
58
|
| Lambda `() -> Unit` | Unstable by default | Captured variables may change |
|
|
59
|
+
| Class from a module not compiled with the Compose compiler (e.g. a domain module) | No | Compose cannot see its fields; map it to a UI model in the feature module, annotate it, or list it in a stability configuration file |
|
|
58
60
|
|
|
59
61
|
Mark truly immutable models `@Immutable` and observable-but-stable state
|
|
60
62
|
`@Stable`. Replace `List`/`Set`/`Map` parameters with
|
|
@@ -68,6 +70,27 @@ See `references/patterns.md` -> "Compose Stability" for the annotation
|
|
|
68
70
|
examples, the immutable-collections `StateFlow` mapping, and the
|
|
69
71
|
`gradle.properties` report settings.
|
|
70
72
|
|
|
73
|
+
## Deferred State Reads
|
|
74
|
+
|
|
75
|
+
Where a state is read decides which phase re-runs when it changes. An animated
|
|
76
|
+
value read during composition recomposes the whole enclosing scope every frame.
|
|
77
|
+
The same value read in a layout or draw lambda re-runs only that phase.
|
|
78
|
+
|
|
79
|
+
| Phase the read happens in | Example | Re-runs |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| Composition | `Modifier.offset(x = v)`, `Modifier.alpha(v)`, `Modifier.scale(v)` | composition, layout, draw |
|
|
82
|
+
| Layout | `Modifier.offset { IntOffset(v.roundToPx(), 0) }` | layout, draw |
|
|
83
|
+
| Draw | `Modifier.graphicsLayer { alpha = v; translationX = px }`, `drawBehind { }` | draw only |
|
|
84
|
+
|
|
85
|
+
- Delegate animation state with `by` (`val offset by animateDpAsState(...)`) and
|
|
86
|
+
read it inside the latest phase that can use it.
|
|
87
|
+
- Animate colors through `drawBehind { drawRect(color) }` rather than a
|
|
88
|
+
`background(color)` read in composition.
|
|
89
|
+
- Hoist a `rememberInfiniteTransition` above a list instead of creating one per
|
|
90
|
+
item.
|
|
91
|
+
- Applies to `animate*AsState`, `Animatable`, `InfiniteTransition` and any state
|
|
92
|
+
that changes every frame (scroll offsets, drag positions).
|
|
93
|
+
|
|
71
94
|
## Recomposition Debugging
|
|
72
95
|
|
|
73
96
|
Use Android Studio's Layout Inspector with "Show Recomposition Counts" enabled
|
|
@@ -88,8 +111,8 @@ Compose cannot diff efficiently and re-renders the whole list on any change.
|
|
|
88
111
|
|
|
89
112
|
```kotlin
|
|
90
113
|
LazyColumn {
|
|
91
|
-
items(items =
|
|
92
|
-
|
|
114
|
+
items(items = orders, key = { it.id }) { order ->
|
|
115
|
+
OrderCard(order = order)
|
|
93
116
|
}
|
|
94
117
|
}
|
|
95
118
|
```
|
|
@@ -212,6 +235,8 @@ debounced search `StateFlow`.
|
|
|
212
235
|
- [ ] No object allocations inside composable body (formatters, regex)
|
|
213
236
|
- [ ] `derivedStateOf` used for computed scroll/visibility state
|
|
214
237
|
- [ ] `remember` used for expensive objects
|
|
238
|
+
- [ ] Animated values read in `offset { }` / `graphicsLayer { }` / `drawBehind { }`, not in composition
|
|
239
|
+
- [ ] No cross-module domain type in a composable parameter or UI state
|
|
215
240
|
- [ ] R8 enabled with `isMinifyEnabled = true` for release
|
|
216
241
|
- [ ] LeakCanary included as `debugImplementation` only
|
|
217
242
|
- [ ] Coil configured with memory and disk cache limits
|