oh-my-agent-commandcode 0.0.0-stage → 20.38.3
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/README.md +35 -2
- package/filter-test-output.sh +52 -0
- package/mods/oma.ts +347 -0
- package/package.json +30 -4
- package/skills/_shared/conditional/experiment-ledger.md +28 -0
- package/skills/_shared/conditional/exploration-loop.md +22 -0
- package/skills/_shared/conditional/quality-score.md +33 -0
- package/skills/_shared/core/anti-ai-prose.md +191 -0
- package/skills/_shared/core/api-contracts/README.md +28 -0
- package/skills/_shared/core/api-contracts/template.md +90 -0
- package/skills/_shared/core/clarification-protocol.md +23 -0
- package/skills/_shared/core/code-intelligence.md +46 -0
- package/skills/_shared/core/collaboration-policy.md +28 -0
- package/skills/_shared/core/common-checklist.md +28 -0
- package/skills/_shared/core/context-budget.md +22 -0
- package/skills/_shared/core/context-loading.md +34 -0
- package/skills/_shared/core/difficulty-guide.md +26 -0
- package/skills/_shared/core/execution-policy.md +31 -0
- package/skills/_shared/core/lessons-learned.md +33 -0
- package/skills/_shared/core/prompt-structure.md +18 -0
- package/skills/_shared/core/quality-principles.md +11 -0
- package/skills/_shared/core/session-metrics.md +35 -0
- package/skills/_shared/core/skill-routing.md +39 -0
- package/skills/_shared/core/test-approach.md +54 -0
- package/skills/_shared/core/vendor-detection.md +52 -0
- package/skills/_shared/runtime/memory-protocol.md +118 -0
- package/skills/_shared/runtime/result-contract.md +64 -0
- package/skills/_shared/ui-interaction-quality.md +133 -0
- package/skills/oma-academic-writing/SKILL.md +181 -0
- package/skills/oma-academic-writing/resources/abstract-guide.md +51 -0
- package/skills/oma-academic-writing/resources/academic-verb-tiers.md +42 -0
- package/skills/oma-academic-writing/resources/anti-ai-checklist.md +52 -0
- package/skills/oma-academic-writing/resources/hedging-guide.md +132 -0
- package/skills/oma-academic-writing/resources/sentence-structure-reference.md +151 -0
- package/skills/oma-architecture/SKILL.md +203 -0
- package/skills/oma-architecture/resources/api-evolution.md +41 -0
- package/skills/oma-architecture/resources/checklist.md +42 -0
- package/skills/oma-architecture/resources/execution-protocol.md +115 -0
- package/skills/oma-architecture/resources/methodology-selection.md +92 -0
- package/skills/oma-architecture/resources/migration-patterns.md +28 -0
- package/skills/oma-architecture/resources/output-templates.md +111 -0
- package/skills/oma-architecture/resources/stakeholder-synthesis.md +80 -0
- package/skills/oma-backend/SKILL.md +202 -0
- package/skills/oma-backend/resources/checklist.md +45 -0
- package/skills/oma-backend/resources/error-playbook.md +97 -0
- package/skills/oma-backend/resources/execution-protocol.md +39 -0
- package/skills/oma-backend/resources/orm-reference.md +61 -0
- package/skills/oma-backend/variants/node/api-template.ts +315 -0
- package/skills/oma-backend/variants/node/snippets.md +475 -0
- package/skills/oma-backend/variants/node/stack.yaml +21 -0
- package/skills/oma-backend/variants/node/tech-stack.md +33 -0
- package/skills/oma-backend/variants/python/api-template.py +378 -0
- package/skills/oma-backend/variants/python/snippets.md +199 -0
- package/skills/oma-backend/variants/python/stack.yaml +18 -0
- package/skills/oma-backend/variants/python/tech-stack.md +45 -0
- package/skills/oma-backend/variants/rust/api-template.rs +635 -0
- package/skills/oma-backend/variants/rust/snippets.md +471 -0
- package/skills/oma-backend/variants/rust/stack.yaml +20 -0
- package/skills/oma-backend/variants/rust/tech-stack.md +36 -0
- package/skills/oma-backend/variants/stack.schema.json +73 -0
- package/skills/oma-brainstorm/SKILL.md +166 -0
- package/skills/oma-brainstorm/resources/triz-lite.md +117 -0
- package/skills/oma-coordination/SKILL.md +171 -0
- package/skills/oma-db/SKILL.md +201 -0
- package/skills/oma-db/resources/anti-patterns.md +177 -0
- package/skills/oma-db/resources/checklist.md +67 -0
- package/skills/oma-db/resources/document-templates.md +107 -0
- package/skills/oma-db/resources/error-playbook.md +50 -0
- package/skills/oma-db/resources/examples.md +170 -0
- package/skills/oma-db/resources/execution-protocol.md +127 -0
- package/skills/oma-db/resources/iso-controls.md +98 -0
- package/skills/oma-db/resources/migration-playbook.md +93 -0
- package/skills/oma-db/resources/query-tuning.md +57 -0
- package/skills/oma-db/resources/vector-db.md +159 -0
- package/skills/oma-debug/SKILL.md +146 -0
- package/skills/oma-debug/resources/bug-report-template.md +327 -0
- package/skills/oma-debug/resources/checklist.md +31 -0
- package/skills/oma-debug/resources/common-patterns.md +734 -0
- package/skills/oma-debug/resources/debugging-checklist.md +364 -0
- package/skills/oma-debug/resources/error-playbook.md +94 -0
- package/skills/oma-debug/resources/execution-protocol.md +40 -0
- package/skills/oma-design/SKILL.md +135 -0
- package/skills/oma-design/resources/asset-strategy.md +34 -0
- package/skills/oma-design/resources/checklist.md +63 -0
- package/skills/oma-design/resources/data-grid.md +48 -0
- package/skills/oma-design/resources/design-md-spec.md +107 -0
- package/skills/oma-design/resources/design-tokens.md +53 -0
- package/skills/oma-design/resources/error-playbook.md +18 -0
- package/skills/oma-design/resources/execution-protocol.md +95 -0
- package/skills/oma-design/resources/external-design-skills.md +130 -0
- package/skills/oma-design/resources/getdesign-fetcher.md +104 -0
- package/skills/oma-design/resources/product-interface-review.md +59 -0
- package/skills/oma-design/resources/redesign-protocol.md +52 -0
- package/skills/oma-design/resources/stitch-integration.md +244 -0
- package/skills/oma-dev-workflow/SKILL.md +375 -0
- package/skills/oma-dev-workflow/resources/affected-checks.py +58 -0
- package/skills/oma-dev-workflow/resources/api-workflows.md +58 -0
- package/skills/oma-dev-workflow/resources/database-patterns.md +107 -0
- package/skills/oma-dev-workflow/resources/i18n-patterns.md +63 -0
- package/skills/oma-dev-workflow/resources/release-coordination.md +240 -0
- package/skills/oma-dev-workflow/resources/tooling-profiles.md +217 -0
- package/skills/oma-dev-workflow/resources/troubleshooting.md +78 -0
- package/skills/oma-dev-workflow/resources/validation-pipeline.md +246 -0
- package/skills/oma-docs/SKILL.md +77 -0
- package/skills/oma-docs/resources/commands.md +76 -0
- package/skills/oma-explanation/SKILL.md +212 -0
- package/skills/oma-explanation/resources/document-structure.md +115 -0
- package/skills/oma-explanation/resources/draft-format.md +199 -0
- package/skills/oma-explanation/resources/html-contract.md +102 -0
- package/skills/oma-frontend/SKILL.md +227 -0
- package/skills/oma-frontend/resources/angular-rules.md +110 -0
- package/skills/oma-frontend/resources/checklist.md +76 -0
- package/skills/oma-frontend/resources/error-playbook.md +125 -0
- package/skills/oma-frontend/resources/execution-protocol.md +46 -0
- package/skills/oma-frontend/resources/snippets.md +354 -0
- package/skills/oma-frontend/resources/specialized-visual-implementation.md +193 -0
- package/skills/oma-frontend/resources/tailwind-rules.md +83 -0
- package/skills/oma-frontend/resources/tech-stack.md +156 -0
- package/skills/oma-hwp/SKILL.md +166 -0
- package/skills/oma-hwp/config/hwp-config.yaml +35 -0
- package/skills/oma-hwp/resources/balanced-tables.ts +39 -0
- package/skills/oma-hwp/resources/execution-protocol.md +145 -0
- package/skills/oma-hwp/resources/flatten-tables.ts +101 -0
- package/skills/oma-hwp/resources/package.json +13 -0
- package/skills/oma-hwp/resources/troubleshooting.md +70 -0
- package/skills/oma-hwp/resources/tsconfig.json +10 -0
- package/skills/oma-hwp/resources/turndown-plugin-gfm.d.ts +9 -0
- package/skills/oma-image/SKILL.md +142 -0
- package/skills/oma-image/config/image-config.yaml +70 -0
- package/skills/oma-image/resources/checklist.md +18 -0
- package/skills/oma-image/resources/execution-protocol.md +117 -0
- package/skills/oma-image/resources/invocation.md +105 -0
- package/skills/oma-image/resources/prompt-tips.md +71 -0
- package/skills/oma-image/resources/vendor-matrix.md +90 -0
- package/skills/oma-market/SKILL.md +142 -0
- package/skills/oma-market/resources/checklist.md +12 -0
- package/skills/oma-market/resources/error-playbook.md +12 -0
- package/skills/oma-market/resources/execution-protocol.md +60 -0
- package/skills/oma-market/resources/frameworks/pestel.md +101 -0
- package/skills/oma-market/resources/frameworks/porters-5f.md +90 -0
- package/skills/oma-market/resources/frameworks/swot.md +61 -0
- package/skills/oma-market/resources/intent-rules.md +35 -0
- package/skills/oma-market/resources/output-laws.md +53 -0
- package/skills/oma-mobile/SKILL.md +168 -0
- package/skills/oma-mobile/resources/checklist.md +77 -0
- package/skills/oma-mobile/resources/error-playbook.md +198 -0
- package/skills/oma-mobile/resources/execution-protocol.md +47 -0
- package/skills/oma-mobile/resources/screen-template.dart +331 -0
- package/skills/oma-mobile/resources/screen-template.swift +239 -0
- package/skills/oma-mobile/resources/screen-template.tsx +179 -0
- package/skills/oma-mobile/resources/tech-stack.md +69 -0
- package/skills/oma-mobile/variants/README.md +45 -0
- package/skills/oma-mobile/variants/flutter/api-template.dart +461 -0
- package/skills/oma-mobile/variants/flutter/snippets.md +1353 -0
- package/skills/oma-mobile/variants/flutter/stack.yaml +21 -0
- package/skills/oma-mobile/variants/flutter/tech-stack.md +201 -0
- package/skills/oma-mobile/variants/kotlin-android/api-template.kt +182 -0
- package/skills/oma-mobile/variants/kotlin-android/snippets.md +871 -0
- package/skills/oma-mobile/variants/kotlin-android/stack.yaml +32 -0
- package/skills/oma-mobile/variants/kotlin-android/tech-stack.md +159 -0
- package/skills/oma-mobile/variants/react-native/api-template.ts +531 -0
- package/skills/oma-mobile/variants/react-native/snippets.md +1014 -0
- package/skills/oma-mobile/variants/react-native/stack.yaml +22 -0
- package/skills/oma-mobile/variants/react-native/tech-stack.md +192 -0
- package/skills/oma-mobile/variants/stack.schema.json +68 -0
- package/skills/oma-mobile/variants/swift-ios/api-template.swift +165 -0
- package/skills/oma-mobile/variants/swift-ios/snippets.md +1028 -0
- package/skills/oma-mobile/variants/swift-ios/stack.yaml +25 -0
- package/skills/oma-mobile/variants/swift-ios/tech-stack.md +174 -0
- package/skills/oma-observability/SKILL.md +225 -0
- package/skills/oma-observability/resources/anti-patterns.md +555 -0
- package/skills/oma-observability/resources/boundaries/cross-application.md +348 -0
- package/skills/oma-observability/resources/boundaries/multi-tenant.md +290 -0
- package/skills/oma-observability/resources/boundaries/release.md +372 -0
- package/skills/oma-observability/resources/boundaries/slo.md +225 -0
- package/skills/oma-observability/resources/checklist.md +123 -0
- package/skills/oma-observability/resources/examples.md +448 -0
- package/skills/oma-observability/resources/execution-protocol.md +166 -0
- package/skills/oma-observability/resources/incident-forensics.md +348 -0
- package/skills/oma-observability/resources/intent-rules.md +307 -0
- package/skills/oma-observability/resources/layers/L3-network.md +281 -0
- package/skills/oma-observability/resources/layers/L4-transport.md +299 -0
- package/skills/oma-observability/resources/layers/L7-application/crash-analytics.md +359 -0
- package/skills/oma-observability/resources/layers/L7-application/mobile-rum.md +278 -0
- package/skills/oma-observability/resources/layers/L7-application/waf.md +246 -0
- package/skills/oma-observability/resources/layers/L7-application/web-rum.md +350 -0
- package/skills/oma-observability/resources/layers/mesh.md +368 -0
- package/skills/oma-observability/resources/matrix.md +177 -0
- package/skills/oma-observability/resources/meta-observability.md +605 -0
- package/skills/oma-observability/resources/observability-as-code.md +666 -0
- package/skills/oma-observability/resources/signals/audit.md +310 -0
- package/skills/oma-observability/resources/signals/cost.md +303 -0
- package/skills/oma-observability/resources/signals/logs.md +263 -0
- package/skills/oma-observability/resources/signals/metrics.md +478 -0
- package/skills/oma-observability/resources/signals/privacy.md +351 -0
- package/skills/oma-observability/resources/signals/profiles.md +245 -0
- package/skills/oma-observability/resources/signals/traces.md +360 -0
- package/skills/oma-observability/resources/standards.md +214 -0
- package/skills/oma-observability/resources/transport/collector-topology.md +308 -0
- package/skills/oma-observability/resources/transport/otlp-grpc-vs-http.md +256 -0
- package/skills/oma-observability/resources/transport/sampling-recipes.md +297 -0
- package/skills/oma-observability/resources/transport/udp-statsd-mtu.md +167 -0
- package/skills/oma-observability/resources/vendor-categories.md +352 -0
- package/skills/oma-orchestration/SKILL.md +302 -0
- package/skills/oma-orchestration/config/cli-config.yaml +92 -0
- package/skills/oma-orchestration/resources/memory-schema.md +232 -0
- package/skills/oma-orchestration/resources/subagent-prompt-template.md +30 -0
- package/skills/oma-orchestration/scripts/parallel-run.sh +6 -0
- package/skills/oma-orchestration/scripts/spawn-agent.sh +5 -0
- package/skills/oma-orchestration/scripts/verify.sh +20 -0
- package/skills/oma-orchestration/templates/backend-task.md +19 -0
- package/skills/oma-orchestration/templates/debug-task.md +16 -0
- package/skills/oma-orchestration/templates/frontend-task.md +18 -0
- package/skills/oma-orchestration/templates/mobile-task.md +18 -0
- package/skills/oma-orchestration/templates/qa-task.md +16 -0
- package/skills/oma-orchestration/templates/tasks-example.yaml +15 -0
- package/skills/oma-pdf/SKILL.md +162 -0
- package/skills/oma-pdf/config/pdf-config.yaml +27 -0
- package/skills/oma-pdf/resources/execution-protocol.md +127 -0
- package/skills/oma-pm/SKILL.md +157 -0
- package/skills/oma-pm/resources/error-playbook.md +75 -0
- package/skills/oma-pm/resources/examples.md +439 -0
- package/skills/oma-pm/resources/execution-protocol.md +62 -0
- package/skills/oma-pm/resources/iso-planning.md +110 -0
- package/skills/oma-pm/resources/plan-phase-protocol.md +66 -0
- package/skills/oma-pm/resources/task-template.json +86 -0
- package/skills/oma-qa/SKILL.md +141 -0
- package/skills/oma-qa/resources/checklist.md +339 -0
- package/skills/oma-qa/resources/error-playbook.md +98 -0
- package/skills/oma-qa/resources/execution-protocol.md +169 -0
- package/skills/oma-qa/resources/iso-quality.md +82 -0
- package/skills/oma-qa/resources/verify-ship-protocol.md +116 -0
- package/skills/oma-recap/SKILL.md +245 -0
- package/skills/oma-recap/resources/output-formats.md +83 -0
- package/skills/oma-refactor/SKILL.md +184 -0
- package/skills/oma-refactor/resources/definition.md +108 -0
- package/skills/oma-refactor/resources/governance.md +33 -0
- package/skills/oma-refactor/resources/hotspots.py +44 -0
- package/skills/oma-refactor/resources/measurement.md +93 -0
- package/skills/oma-scholar/SKILL.md +276 -0
- package/skills/oma-scholar/config/scholar-config.yaml +79 -0
- package/skills/oma-scholar/resources/api-endpoints.md +174 -0
- package/skills/oma-scholar/resources/checklist.md +92 -0
- package/skills/oma-scholar/resources/execution-protocol.md +224 -0
- package/skills/oma-scholar/resources/fallback-providers.md +143 -0
- package/skills/oma-scholar/resources/setup-openalex.md +85 -0
- package/skills/oma-scholar/resources/sidecar-spec.md +341 -0
- package/skills/oma-scholar/resources/upstream-spec-cache.md +972 -0
- package/skills/oma-scm/SKILL.md +103 -0
- package/skills/oma-scm/resources/cm-operations.md +56 -0
- package/skills/oma-scm/resources/codeowners-playbook.md +45 -0
- package/skills/oma-scm/resources/conventional-commits.md +108 -0
- package/skills/oma-scm/resources/merge-risk.md +76 -0
- package/skills/oma-scm/resources/onboarding-risk-signals.md +71 -0
- package/skills/oma-search/SKILL.md +125 -0
- package/skills/oma-search/resources/checklist.md +36 -0
- package/skills/oma-search/resources/error-playbook.md +66 -0
- package/skills/oma-search/resources/examples.md +80 -0
- package/skills/oma-search/resources/execution-protocol.md +86 -0
- package/skills/oma-search/resources/intent-rules.md +47 -0
- package/skills/oma-search/resources/trust-registry.md +53 -0
- package/skills/oma-security/SKILL.md +172 -0
- package/skills/oma-security/resources/artex.md +108 -0
- package/skills/oma-security/resources/ci.md +134 -0
- package/skills/oma-security/resources/cisco-source.md +108 -0
- package/skills/oma-security/resources/deepsec-config.md +45 -0
- package/skills/oma-security/resources/deepsec-matchers.md +35 -0
- package/skills/oma-security/resources/deepsec-scanning.md +45 -0
- package/skills/oma-security/resources/deepsec-setup.md +41 -0
- package/skills/oma-security/resources/findings-contract.md +408 -0
- package/skills/oma-security/resources/mcp-scanning.md +122 -0
- package/skills/oma-security/resources/skill-scanning.md +106 -0
- package/skills/oma-security/resources/validation.md +71 -0
- package/skills/oma-skill-creation/SKILL.md +84 -0
- package/skills/oma-skill-creation/resources/prompt-evaluation.md +36 -0
- package/skills/oma-skill-creation/resources/ssl-lite-template.md +108 -0
- package/skills/oma-skill-creation/resources/validation-checklist.md +101 -0
- package/skills/oma-slide/SKILL.md +277 -0
- package/skills/oma-slide/resources/animation-patterns.md +245 -0
- package/skills/oma-slide/resources/assets/deck-stage.js +514 -0
- package/skills/oma-slide/resources/assets/viewport-base.css +257 -0
- package/skills/oma-slide/resources/canva-integration.md +203 -0
- package/skills/oma-slide/resources/checklist.md +46 -0
- package/skills/oma-slide/resources/design-doctrine.md +260 -0
- package/skills/oma-slide/resources/fixed-stage.md +232 -0
- package/skills/oma-slide/resources/generation-protocol.md +393 -0
- package/skills/oma-slide/resources/native-pptx.md +84 -0
- package/skills/oma-slide/resources/selection-index.json +461 -0
- package/skills/oma-slide/resources/style-presets.LICENSE +10 -0
- package/skills/oma-slide/resources/style-presets.md +520 -0
- package/skills/oma-tf-infra/SKILL.md +201 -0
- package/skills/oma-tf-infra/resources/checklist.md +53 -0
- package/skills/oma-tf-infra/resources/cost-optimization.md +574 -0
- package/skills/oma-tf-infra/resources/error-playbook.md +68 -0
- package/skills/oma-tf-infra/resources/examples.md +38 -0
- package/skills/oma-tf-infra/resources/execution-protocol.md +129 -0
- package/skills/oma-tf-infra/resources/iso-42001-infra.md +229 -0
- package/skills/oma-tf-infra/resources/multi-cloud-examples.md +241 -0
- package/skills/oma-tf-infra/resources/policy-testing-examples.md +332 -0
- package/skills/oma-translation/SKILL.md +229 -0
- package/skills/oma-translation/resources/anti-ai-patterns.md +104 -0
- package/skills/oma-translation/resources/lang/_template.md +73 -0
- package/skills/oma-translation/resources/lang/en.md +123 -0
- package/skills/oma-translation/resources/lang/ja.md +274 -0
- package/skills/oma-translation/resources/lang/ko.md +228 -0
- package/skills/oma-translation/resources/lang/zh.md +285 -0
- package/skills/oma-translation/resources/style-skills.md +104 -0
- package/skills/oma-translation/resources/translation-rubric.md +126 -0
- package/skills/oma-video/SKILL.md +229 -0
- package/skills/oma-video/config/video-config.yaml +60 -0
- package/skills/oma-video/resources/checklist.md +26 -0
- package/skills/oma-video/resources/execution-protocol.md +122 -0
- package/skills/oma-video/resources/existing-media.md +185 -0
- package/skills/oma-video/resources/hyperframes-authoring/README.md +44 -0
- package/skills/oma-video/resources/hyperframes-authoring/demo.md +31 -0
- package/skills/oma-video/resources/hyperframes-authoring/explainer.md +10 -0
- package/skills/oma-video/resources/hyperframes-authoring/shorts.md +10 -0
- package/skills/oma-video/resources/motion-principles.md +81 -0
- package/skills/oma-video/resources/mpt/driver.py +301 -0
- package/skills/oma-video/resources/prompt-tips.md +75 -0
- package/skills/oma-video/resources/script-schema.md +81 -0
- package/skills/oma-video/resources/sound-design.md +127 -0
- package/skills/oma-video/resources/strudel/package.json +11 -0
- package/skills/oma-video/resources/strudel/page.html +87 -0
- package/skills/oma-video/resources/strudel/render.mjs +295 -0
- package/skills/oma-video/resources/vendor-matrix.md +124 -0
- package/skills/oma-voice/SKILL.md +249 -0
- package/skills/oma-voice/config/voice-config.yaml +30 -0
- package/skills/oma-voice/resources/audio-processing.md +119 -0
- package/skills/oma-voice/resources/checklist.md +47 -0
- package/skills/oma-voice/resources/execution-protocol.md +215 -0
- package/skills/oma-voice/resources/prompt-tips.md +43 -0
- package/skills/oma-voice/resources/voice-matrix.md +51 -0
- package/skills/oma-voice/resources/voicestudio.md +87 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# yaml-language-server: $schema=../stack.schema.json
|
|
2
|
+
language: flutter
|
|
3
|
+
framework: flutter
|
|
4
|
+
ui: material3
|
|
5
|
+
state: riverpod
|
|
6
|
+
navigation: gorouter
|
|
7
|
+
http_client: dio
|
|
8
|
+
local_storage: drift
|
|
9
|
+
response_cache: drift-offline-repo
|
|
10
|
+
structure: lib/core/features/shared
|
|
11
|
+
source: variant
|
|
12
|
+
|
|
13
|
+
verify:
|
|
14
|
+
detect: pubspec.yaml
|
|
15
|
+
syntax:
|
|
16
|
+
cmd: "dart analyze"
|
|
17
|
+
skip_if_missing: dart
|
|
18
|
+
tests:
|
|
19
|
+
cmd: "flutter test"
|
|
20
|
+
pass_signal: "All tests passed"
|
|
21
|
+
skip_if_missing: flutter
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Mobile Agent - Tech Stack Reference (Flutter)
|
|
2
|
+
|
|
3
|
+
Starter reference for the selected platform. Preserve existing project choices. The caching implementation below applies only when caching is required; do not add it for an unrelated screen or widget change.
|
|
4
|
+
|
|
5
|
+
## Framework + State Management
|
|
6
|
+
|
|
7
|
+
- **Language**: Dart 3.10+
|
|
8
|
+
- **UI Framework**: Flutter 3.44+
|
|
9
|
+
- **UI Toolkit**: Material Design 3 (Android) + iOS HIG adaptations via `CupertinoAdaptiveTheme` or `Platform.isIOS` guards
|
|
10
|
+
- **State Management**: Riverpod 3.x with `@riverpod` code generation (`AsyncNotifier`, `Notifier`, `StreamNotifier`)
|
|
11
|
+
- **Concurrency**: Dart async/await, `Stream`, `StreamController`, structured cancellation via `ref.onDispose`
|
|
12
|
+
- **Minimum SDK**: Flutter 3.44 / Dart 3.10 / Android API 24 / iOS 16
|
|
13
|
+
|
|
14
|
+
Riverpod 3.x with `@riverpod` codegen replaces manual `StateNotifierProvider` and hand-written provider constants. The code generator emits typed, cancellation-safe notifier subclasses at build time (`AsyncNotifier` for `Future` builds, `StreamNotifier` for `Stream` builds). Riverpod 3 drops the generated `FooRef` typedefs — provider and notifier build signatures take the unified `Ref` type. Provider state is disposed automatically when the last listener is removed; side-effectful providers use `ref.onDispose` to cancel in-flight requests.
|
|
15
|
+
|
|
16
|
+
## HTTP / API Client: Dio with Interceptors
|
|
17
|
+
|
|
18
|
+
| Component | Package |
|
|
19
|
+
|-----------|---------|
|
|
20
|
+
| HTTP transport | `dio` |
|
|
21
|
+
| Auth interceptor | custom `AuthInterceptor` (injects Bearer token) |
|
|
22
|
+
| Retry interceptor | custom `RetryInterceptor` (exponential back-off) |
|
|
23
|
+
| Logging interceptor | `pretty_dio_logger` (debug builds only) |
|
|
24
|
+
|
|
25
|
+
Dio is the **transport layer only**. Interceptors handle cross-cutting concerns (auth injection, retry, logging) — they must never cache or interpret domain objects. All Dio interceptors operate on raw `RequestOptions` / `Response` bytes. Domain-level caching belongs exclusively at the Repository layer (see below).
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Presentation (Riverpod provider)
|
|
29
|
+
| calls
|
|
30
|
+
v
|
|
31
|
+
Repository (data layer) ──reads/writes──► Local Cache (Drift table)
|
|
32
|
+
| on miss / revalidate
|
|
33
|
+
v
|
|
34
|
+
RemoteDataSource (Dio) ──HTTP──► Backend REST API
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Response Cache: Offline-First Repository (Drift)
|
|
38
|
+
|
|
39
|
+
When response caching is required, implement it at the Repository (data) layer, using a Drift database table as the local cache. The cache stores **decoded domain entities** — never raw HTTP bytes or JSON strings. The Dio client and its interceptors are never cache-aware.
|
|
40
|
+
|
|
41
|
+
**Placement rule — Repository layer, not transport.** Do **not** add a Dio cache interceptor (e.g. `dio_http_cache`, `dio_cache_interceptor`) as the system of record for domain reads. Such interceptors operate on raw bytes, making them invisible to the domain and untestable at the business-logic level. Cache the typed result *after* decoding instead.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
View (Flutter Widget)
|
|
45
|
+
| watches
|
|
46
|
+
v
|
|
47
|
+
Riverpod StreamNotifier (features/<feature>/presentation/)
|
|
48
|
+
| calls
|
|
49
|
+
v
|
|
50
|
+
Repository interface (features/<feature>/domain/)
|
|
51
|
+
| reads/writes (stale-while-revalidate)
|
|
52
|
+
+──────────────────► Drift table (local cache of decoded entities)
|
|
53
|
+
| on miss / revalidate
|
|
54
|
+
v
|
|
55
|
+
RemoteDataSource / Dio (features/<feature>/data/)
|
|
56
|
+
| HTTP
|
|
57
|
+
v
|
|
58
|
+
Backend REST API
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Mandatory rules:**
|
|
62
|
+
|
|
63
|
+
1. Every read operation goes through the Repository, which queries Drift first and returns a `Stream` (or `Future`) of cached entities. The Repository then triggers a background network revalidation and upserts the result back into Drift — the stream re-emits automatically.
|
|
64
|
+
2. Cache **key** = logical operation ID + a stable hash of its parameters (e.g., `"todos"`, `"todo:$id"`). Never key on URLs or raw path strings.
|
|
65
|
+
3. Apply a **stale-while-revalidate** policy: emit the locally-cached rows immediately (instant render), then fetch from the network, upsert into Drift, and let the stream re-emit the fresh rows. Store an explicit `cachedAt` timestamp column; check it against a per-resource TTL before deciding to revalidate.
|
|
66
|
+
4. Any **write** (create / update / delete) must upsert or delete the affected Drift rows after a successful network response, so the next stream emission reflects the change immediately without a separate read round-trip.
|
|
67
|
+
5. Drift is for transient **server-owned** data caches (and optionally durable user-owned data). **Secrets** (tokens, credentials) still belong in `flutter_secure_storage`. Do not use the cache table as a system of record for user-authored drafts — use a separate Drift table with a different retention policy.
|
|
68
|
+
|
|
69
|
+
See `snippets.md §3` for the canonical offline-first repository and `snippets.md §4` for the Riverpod `StreamNotifier` consuming it.
|
|
70
|
+
|
|
71
|
+
## Local Storage
|
|
72
|
+
|
|
73
|
+
| Library | Purpose |
|
|
74
|
+
|---------|---------|
|
|
75
|
+
| `drift` | Durable structured persistence (system-of-record user data **and** the offline response cache table) |
|
|
76
|
+
| `flutter_secure_storage` | Tokens, credentials, and secrets |
|
|
77
|
+
| `shared_preferences` | Lightweight key-value preferences (theme, locale, flags) |
|
|
78
|
+
| `hive` / `hive_flutter` | Lightweight alternative to Drift for simple caches where SQL is unnecessary |
|
|
79
|
+
|
|
80
|
+
**Drift vs Hive for the cache table:** Drift is the default because it provides SQL queries, type-safe DAOs, and Dart `Stream`s that auto-emit on row changes — exactly what the stale-while-revalidate pattern needs. Use Hive when the cached type is a simple flat object and you want to avoid the Drift codegen dependency. Either way, the Repository interface is unchanged; only the impl swaps out. This stack uses Drift (`response_cache: drift-offline-repo`).
|
|
81
|
+
|
|
82
|
+
## Testing
|
|
83
|
+
|
|
84
|
+
| Layer | Framework |
|
|
85
|
+
|-------|-----------|
|
|
86
|
+
| Unit (domain + repository) | `flutter_test` + `mocktail` |
|
|
87
|
+
| Widget | `flutter_test` (`WidgetTester`) |
|
|
88
|
+
| Integration | `integration_test` package |
|
|
89
|
+
| E2E | Maestro (`maestro test`) |
|
|
90
|
+
|
|
91
|
+
Mock the `Repository` **interface** (abstract class), not the concrete implementation or Dio. `mocktail` stubs are preferred over hand-written fakes for brevity, but both are acceptable. Never mock Drift internals — test the real Drift DB in-memory (`NativeDatabase.memory()`) for repository-layer tests.
|
|
92
|
+
|
|
93
|
+
Run tests with `flutter test` (unit + widget) and `flutter test integration_test/` (integration). Maestro `.yaml` flow files live in `maestro/` at the project root; see `snippets.md §11` for a runnable launch → add → assert flow.
|
|
94
|
+
|
|
95
|
+
## Project Layout
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
my_app/
|
|
99
|
+
pubspec.yaml # deps + build_runner config
|
|
100
|
+
pubspec.lock
|
|
101
|
+
build.yaml # build_runner options (riverpod_generator, drift)
|
|
102
|
+
maestro/ # Maestro E2E flow files
|
|
103
|
+
todos_flow.yaml
|
|
104
|
+
lib/
|
|
105
|
+
main.dart # runApp entry; ProviderScope at root
|
|
106
|
+
core/
|
|
107
|
+
di/
|
|
108
|
+
providers.dart # App-wide singleton providers (Dio, DB, SecureStorage)
|
|
109
|
+
network/
|
|
110
|
+
dio_client.dart # Dio factory + AuthInterceptor + RetryInterceptor (all in one file; see §5)
|
|
111
|
+
database/
|
|
112
|
+
app_database.dart # Drift @DriftDatabase definition
|
|
113
|
+
app_database.g.dart # generated
|
|
114
|
+
router/
|
|
115
|
+
app_router.dart # GoRouter typed routes
|
|
116
|
+
app_router.g.dart # generated (go_router_builder)
|
|
117
|
+
theme/
|
|
118
|
+
app_theme.dart
|
|
119
|
+
features/
|
|
120
|
+
todos/
|
|
121
|
+
domain/
|
|
122
|
+
todo.dart # Domain entity (freezed or plain Dart)
|
|
123
|
+
todo_repository.dart # Abstract repository interface
|
|
124
|
+
data/
|
|
125
|
+
dtos/
|
|
126
|
+
todo_dto.dart # JSON <-> entity mapping
|
|
127
|
+
local/
|
|
128
|
+
todos_dao.dart # Drift DAO for todos cache table
|
|
129
|
+
todos_dao.g.dart # generated
|
|
130
|
+
remote/
|
|
131
|
+
todos_remote_data_source.dart # Dio calls
|
|
132
|
+
todo_repository_impl.dart # Offline-first impl; cache + remote
|
|
133
|
+
presentation/
|
|
134
|
+
providers/
|
|
135
|
+
todos_provider.dart # @riverpod StreamNotifier
|
|
136
|
+
todos_provider.g.dart # generated
|
|
137
|
+
screens/
|
|
138
|
+
todos_screen.dart
|
|
139
|
+
todo_detail_screen.dart
|
|
140
|
+
widgets/
|
|
141
|
+
todo_list_item.dart
|
|
142
|
+
shared/
|
|
143
|
+
widgets/
|
|
144
|
+
loading_indicator.dart
|
|
145
|
+
empty_state.dart
|
|
146
|
+
error_view.dart
|
|
147
|
+
test/
|
|
148
|
+
features/
|
|
149
|
+
todos/
|
|
150
|
+
todo_repository_impl_test.dart # in-memory Drift DB
|
|
151
|
+
todos_provider_test.dart # mocked repository interface
|
|
152
|
+
todos_screen_test.dart # WidgetTester
|
|
153
|
+
integration_test/
|
|
154
|
+
todos_integration_test.dart
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Architecture Pattern
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
todos_screen.dart (ConsumerWidget)
|
|
161
|
+
| ref.watch(todosProvider)
|
|
162
|
+
v
|
|
163
|
+
todosProvider (@riverpod StreamNotifier<List<Todo>>)
|
|
164
|
+
| ref.watch(todoRepositoryProvider)
|
|
165
|
+
v
|
|
166
|
+
TodoRepository (abstract interface in domain/)
|
|
167
|
+
| watchAll() · findById(id) · create(title) · toggle(id) · delete(id)
|
|
168
|
+
| implemented by TodoRepositoryImpl (data/)
|
|
169
|
+
|
|
|
170
|
+
+──── TodosDao (Drift) ◄──────── local cache of decoded Todo entities
|
|
171
|
+
| Stream<List<TodosTableData>> watchAll()
|
|
172
|
+
| Future<TodosTableData?> findById(int id)
|
|
173
|
+
| Future<void> replaceAll(List<TodosTableCompanion>) // mirrors full server list
|
|
174
|
+
| Future<void> upsertOne(TodosTableCompanion)
|
|
175
|
+
| Future<void> deleteById(int id)
|
|
176
|
+
|
|
|
177
|
+
+──── TodosRemoteDataSource (Dio)
|
|
178
|
+
Future<List<TodoDto>> fetchAll()
|
|
179
|
+
Future<TodoDto> fetchById(int id)
|
|
180
|
+
Future<TodoDto> create(String title)
|
|
181
|
+
Future<TodoDto> toggle(int id)
|
|
182
|
+
Future<void> delete(int id)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Each `features/<name>/` folder is a vertical slice owning its domain, data, and presentation sub-layers. `shared/` holds stateless reusable widgets with no feature knowledge. `core/` holds app-wide infrastructure (Dio, Drift DB, GoRouter, DI providers).
|
|
186
|
+
|
|
187
|
+
The `@riverpod` notifier calls the repository directly — there is no use-case/interactor layer in this stack. Add a `usecases/` class only when a single user action coordinates multiple repositories or carries non-trivial orchestration logic; a straight pass-through to one repository method does not warrant one.
|
|
188
|
+
|
|
189
|
+
<!-- oma-docs:ignore-start -->
|
|
190
|
+
**Sanctioned layering exception (Drift).** `core/database/app_database.dart` imports the feature DAOs (e.g. `features/todos/data/local/todos_dao.dart`) to register them on `@DriftDatabase(daos: [...])`. This is a `core → features` upward import, the reverse of the normal dependency direction. It is the single allowed exception: Drift requires the database class to reference its tables and DAOs for code generation. No other `core` code may import from `features/`.
|
|
191
|
+
<!-- oma-docs:ignore-end -->
|
|
192
|
+
|
|
193
|
+
## Navigation: GoRouter with Typed Routes
|
|
194
|
+
|
|
195
|
+
<!-- oma-docs:ignore-start -->
|
|
196
|
+
`go_router` 17.x with `go_router_builder` (typed route classes generated at build time via `build_runner`) is the navigation layer. Define route classes annotated with `@TypedGoRoute`; the generator emits `.g.dart` files with `push()` / `go()` helpers. `go_router_builder` is a dev-only builder — the `@TypedGoRoute` annotation and `GoRouteData` base class are imported from `package:go_router/go_router.dart`, never from `go_router_builder`.
|
|
197
|
+
<!-- oma-docs:ignore-end -->
|
|
198
|
+
|
|
199
|
+
Route guards (auth checks, onboarding redirects) are handled by `GoRouter.redirect` callbacks, not inside screens. Deep links are declared in `GoRoute.path` and handled by the platform-level `AndroidManifest.xml` / `Info.plist` intent filters.
|
|
200
|
+
|
|
201
|
+
See `snippets.md §2` for the typed GoRouter setup.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Repository Template for Mobile Agent (Kotlin Android Native)
|
|
3
|
+
*
|
|
4
|
+
* This file wraps the generated `TodosApi` produced by the OpenAPI Generator Gradle
|
|
5
|
+
* plugin (`generatorName = "kotlin"`, `library = "jvm-ktor"`) from the committed
|
|
6
|
+
* server contract (`{api_spec}`). It is the **only** place that calls the backend
|
|
7
|
+
* API — never assemble URL paths or JSON bodies by hand for endpoints covered by
|
|
8
|
+
* the OpenAPI spec.
|
|
9
|
+
*
|
|
10
|
+
* Dependencies (all generated into app/build/generated/openapi at `openApiGenerate`):
|
|
11
|
+
* - `…generated.api.TodosApi` — one class per OpenAPI tag (`DefaultApi` when untagged)
|
|
12
|
+
* - `…generated.model.*` — kotlinx-serialization DTOs
|
|
13
|
+
* - `…generated.infrastructure.HttpResponse<T>` — `success`, `status`, `body()`, raw `response`
|
|
14
|
+
*
|
|
15
|
+
* Transport: the generated client talks to a Ktor `HttpClient`. Ktor is an
|
|
16
|
+
* engine-agnostic API layer; the app injects the OkHttp engine (`ktor-client-okhttp`)
|
|
17
|
+
* through Hilt and tests inject `MockEngine` (`ktor-client-mock`). This file never
|
|
18
|
+
* references either engine.
|
|
19
|
+
*
|
|
20
|
+
* Caching: when caching is required, it is MANDATORY at this Repository layer via a
|
|
21
|
+
* Room table of DECODED models (see snippets.md §8) — never an OkHttp `Cache` or a
|
|
22
|
+
* Ktor `HttpCache` plugin. Reads emit cached rows then revalidate; writes upsert or
|
|
23
|
+
* delete the affected rows after the server confirms.
|
|
24
|
+
*
|
|
25
|
+
* File layout (split into real files in production):
|
|
26
|
+
* api-client/src/main/kotlin/<pkg>/client/
|
|
27
|
+
* ApiJson.kt <- shared Json codec (BigDecimal contextual serializer)
|
|
28
|
+
* app/src/main/java/<pkg>/
|
|
29
|
+
* di/NetworkModule.kt <- Ktor client + OkHttp engine → TodosApi (Hilt)
|
|
30
|
+
* di/DatabaseModule.kt <- Room database + DAO providers
|
|
31
|
+
* data/ApiFailure.kt <- typed failures surfaced to view models
|
|
32
|
+
* data/TodoRepository.kt <- this file
|
|
33
|
+
* data/cache/TodoCache.kt <- Room entity + DAO for the response cache
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// app/src/main/java/com/example/app/data/TodoRepository.kt
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
package com.example.app.data
|
|
41
|
+
|
|
42
|
+
import com.example.app.data.cache.TodoCacheDao
|
|
43
|
+
import com.example.app.data.cache.TodoCacheRow
|
|
44
|
+
import com.example.generated.api.TodosApi
|
|
45
|
+
import com.example.generated.infrastructure.HttpResponse
|
|
46
|
+
import com.example.generated.model.CreateTodoRequest
|
|
47
|
+
import com.example.generated.model.Todo
|
|
48
|
+
import io.ktor.client.statement.bodyAsText
|
|
49
|
+
import java.io.IOException
|
|
50
|
+
import javax.inject.Inject
|
|
51
|
+
import javax.inject.Singleton
|
|
52
|
+
import kotlin.time.Duration.Companion.minutes
|
|
53
|
+
import kotlinx.coroutines.CancellationException
|
|
54
|
+
import kotlinx.coroutines.flow.Flow
|
|
55
|
+
import kotlinx.coroutines.flow.map
|
|
56
|
+
import kotlinx.coroutines.flow.onStart
|
|
57
|
+
import kotlinx.serialization.json.Json
|
|
58
|
+
import kotlinx.serialization.json.jsonObject
|
|
59
|
+
import kotlinx.serialization.json.jsonPrimitive
|
|
60
|
+
|
|
61
|
+
/** Typed failures surfaced by [TodoRepository]. View models map these to user text. */
|
|
62
|
+
sealed class ApiFailure(message: String) : Exception(message) {
|
|
63
|
+
class NotFound : ApiFailure("The requested todo was not found.")
|
|
64
|
+
class Conflict(val detail: String) : ApiFailure(detail.ifBlank { "A todo with that title already exists." })
|
|
65
|
+
class Unauthorized : ApiFailure("Session expired.")
|
|
66
|
+
class Server(val status: Int) : ApiFailure("Unexpected server response: HTTP $status.")
|
|
67
|
+
class Network(cause: Throwable) : ApiFailure(cause.message ?: "Network error") {
|
|
68
|
+
init { initCause(cause) }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Interface seam the view models depend on, so the cached [TodoRepository] is
|
|
74
|
+
* swappable for a fake in unit tests (no mocking library required).
|
|
75
|
+
*/
|
|
76
|
+
interface TodoDataSource {
|
|
77
|
+
fun observeTodos(): Flow<List<Todo>>
|
|
78
|
+
suspend fun createTodo(title: String): Todo
|
|
79
|
+
suspend fun toggleTodo(id: String): Todo
|
|
80
|
+
suspend fun deleteTodo(id: String)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* CRUD repository for the `/todos` resource.
|
|
85
|
+
*
|
|
86
|
+
* Depends on the generated [TodosApi] (constructed in `di/NetworkModule.kt` with the
|
|
87
|
+
* OkHttp engine) and a Room [TodoCacheDao]. Inject through Hilt; never instantiate
|
|
88
|
+
* in composables.
|
|
89
|
+
*
|
|
90
|
+
* Caching contract:
|
|
91
|
+
* - Reads emit DECODED cached rows first, then revalidate when older than [ttl].
|
|
92
|
+
* - Writes upsert/delete the affected rows after success so Room re-emits.
|
|
93
|
+
*/
|
|
94
|
+
@Singleton
|
|
95
|
+
class TodoRepository @Inject constructor(
|
|
96
|
+
private val api: TodosApi,
|
|
97
|
+
private val dao: TodoCacheDao,
|
|
98
|
+
private val json: Json,
|
|
99
|
+
private val clock: () -> Long = System::currentTimeMillis,
|
|
100
|
+
) : TodoDataSource {
|
|
101
|
+
|
|
102
|
+
private val ttl = 5.minutes.inWholeMilliseconds
|
|
103
|
+
|
|
104
|
+
// -- List (stale-while-revalidate) ---------------------------------------
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Emits the cached list immediately (if present), then the freshly fetched
|
|
108
|
+
* list once revalidation upserts into Room. With a warm cache a network
|
|
109
|
+
* failure keeps the stale rows and is swallowed; with an empty cache the
|
|
110
|
+
* failure propagates to the collector.
|
|
111
|
+
*/
|
|
112
|
+
override fun observeTodos(): Flow<List<Todo>> = dao.observeAll()
|
|
113
|
+
.map { rows -> rows.map { it.toModel() } }
|
|
114
|
+
.onStart { revalidateIfStale() }
|
|
115
|
+
|
|
116
|
+
private suspend fun revalidateIfStale() {
|
|
117
|
+
val oldest = dao.oldestFetchedAt()
|
|
118
|
+
if (oldest != null && clock() - oldest < ttl) return
|
|
119
|
+
try {
|
|
120
|
+
val fresh = call { api.listTodos() }
|
|
121
|
+
dao.replaceAll(fresh.map { it.toRow(clock()) })
|
|
122
|
+
} catch (failure: ApiFailure) {
|
|
123
|
+
if (oldest == null) throw failure
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// -- Create ------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
override suspend fun createTodo(title: String): Todo {
|
|
130
|
+
val created = call { api.createTodo(CreateTodoRequest(title = title)) }
|
|
131
|
+
dao.upsert(created.toRow(clock()))
|
|
132
|
+
return created
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// -- Toggle ------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
override suspend fun toggleTodo(id: String): Todo {
|
|
138
|
+
val updated = call { api.toggleTodo(id) }
|
|
139
|
+
dao.upsert(updated.toRow(clock()))
|
|
140
|
+
return updated
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// -- Delete ------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
override suspend fun deleteTodo(id: String) {
|
|
146
|
+
call { api.deleteTodo(id) }
|
|
147
|
+
dao.delete(id)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// -- Generated-response unwrapping ---------------------------------------------
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Unwraps the generated [HttpResponse]: non-2xx becomes a typed [ApiFailure],
|
|
154
|
+
* transport `IOException` becomes [ApiFailure.Network], cancellation is rethrown.
|
|
155
|
+
* `expectSuccess = false` is set on the client so Ktor never throws on status.
|
|
156
|
+
*/
|
|
157
|
+
private suspend fun <T> call(request: suspend () -> HttpResponse<T>): T {
|
|
158
|
+
val response = try {
|
|
159
|
+
request()
|
|
160
|
+
} catch (cancelled: CancellationException) {
|
|
161
|
+
throw cancelled
|
|
162
|
+
} catch (io: IOException) {
|
|
163
|
+
throw ApiFailure.Network(io)
|
|
164
|
+
}
|
|
165
|
+
if (!response.success) {
|
|
166
|
+
val detail = runCatching {
|
|
167
|
+
json.parseToJsonElement(response.response.bodyAsText())
|
|
168
|
+
.jsonObject["detail"]?.jsonPrimitive?.content
|
|
169
|
+
}.getOrNull().orEmpty()
|
|
170
|
+
throw when (response.status) {
|
|
171
|
+
401 -> ApiFailure.Unauthorized()
|
|
172
|
+
404 -> ApiFailure.NotFound()
|
|
173
|
+
409 -> ApiFailure.Conflict(detail)
|
|
174
|
+
else -> ApiFailure.Server(response.status)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return response.body()
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private fun TodoCacheRow.toModel() = Todo(id = id, title = title, completed = completed)
|
|
182
|
+
private fun Todo.toRow(now: Long) = TodoCacheRow(id = id, title = title, completed = completed, fetchedAt = now)
|