@michaelbel/cuckcoder-mcp 1.6.11
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/assets/rules/architecture.md +15 -0
- package/assets/rules/bottom-sheet.md +20 -0
- package/assets/rules/compose-color.md +22 -0
- package/assets/rules/compose-constraintlayout.md +18 -0
- package/assets/rules/compose-screen.md +33 -0
- package/assets/rules/compose-spacing.md +39 -0
- package/assets/rules/compose.md +54 -0
- package/assets/rules/dialog.md +33 -0
- package/assets/rules/domain.md +36 -0
- package/assets/rules/filesystem.md +3 -0
- package/assets/rules/git.md +11 -0
- package/assets/rules/github-readme.md +97 -0
- package/assets/rules/github-repo.md +65 -0
- package/assets/rules/kmp.md +14 -0
- package/assets/rules/kotlin.md +48 -0
- package/assets/rules/lazylist.md +36 -0
- package/assets/rules/mvi-error-handling.md +19 -0
- package/assets/rules/mvi-state.md +31 -0
- package/assets/rules/mvi.md +39 -0
- package/assets/rules/navigation.md +20 -0
- package/assets/rules/network.md +36 -0
- package/assets/rules/preview.md +31 -0
- package/assets/rules/realtime.md +39 -0
- package/assets/rules/resource.md +18 -0
- package/assets/rules/room.md +33 -0
- package/assets/rules/scaffold.md +25 -0
- package/assets/rules/shimmer.md +34 -0
- package/assets/rules/textfield.md +31 -0
- package/assets/rules/typography.md +32 -0
- package/assets/rules/usecase.md +60 -0
- package/assets/rules/workflow.md +17 -0
- package/assets/rules/workmanager.md +44 -0
- package/assets/skills/create-data-layer/SKILL.md +60 -0
- package/assets/skills/create-datastore-preference/SKILL.md +80 -0
- package/assets/skills/create-domain-mapper/SKILL.md +30 -0
- package/assets/skills/create-feature-alert-dialog/SKILL.md +549 -0
- package/assets/skills/create-feature-bottom-sheet/SKILL.md +300 -0
- package/assets/skills/create-feature-scaffold-screen/SKILL.md +74 -0
- package/assets/skills/create-feature-scaffold-screen/references/form-screen.md +20 -0
- package/assets/skills/create-feature-scaffold-screen/references/network-only-screen.md +20 -0
- package/assets/skills/create-feature-scaffold-screen/references/paginated-screen.md +29 -0
- package/assets/skills/create-feature-scaffold-screen/references/room-backed-screen.md +23 -0
- package/assets/skills/create-feature-scaffold-screen/references/static-screen.md +13 -0
- package/assets/skills/create-guide/SKILL.md +169 -0
- package/assets/skills/create-guide/references/android-project.md +120 -0
- package/assets/skills/create-guide/references/androidx.md +49 -0
- package/assets/skills/create-guide/references/existing-guides.yaml +60 -0
- package/assets/skills/create-guide/references/github.md +71 -0
- package/assets/skills/create-guide/references/guide-defaults.yaml +51 -0
- package/assets/skills/create-guide/references/notion.md +121 -0
- package/assets/skills/create-guide/references/output-contract.md +79 -0
- package/assets/skills/create-guide/references/research.md +64 -0
- package/assets/skills/create-guide/references/validation.md +59 -0
- package/assets/skills/create-guide/scripts/validate_guide.py +167 -0
- package/assets/skills/create-guide/templates/guide-manifest.yaml +67 -0
- package/assets/skills/create-guide/templates/implementation-plan.md +30 -0
- package/assets/skills/create-guide/templates/notion-page.md +46 -0
- package/assets/skills/create-guide/templates/repository/AGENTS.md +7 -0
- package/assets/skills/create-guide/templates/repository/README.md +32 -0
- package/assets/skills/create-guide/templates/repository/SOURCES.md +32 -0
- package/assets/skills/create-guide/templates/validation-report.md +30 -0
- package/assets/skills/create-guide/tests/test_validate_guide.py +97 -0
- package/assets/skills/create-ktor-endpoint/SKILL.md +33 -0
- package/assets/skills/create-notification-flow/SKILL.md +103 -0
- package/assets/skills/create-offline-outbox/SKILL.md +110 -0
- package/assets/skills/create-paging-flow/SKILL.md +96 -0
- package/assets/skills/create-project-from-template/SKILL.md +91 -0
- package/assets/skills/create-project-from-template/assets/icons/android.svg +12 -0
- package/assets/skills/create-project-from-template/assets/icons/compose.svg +34 -0
- package/assets/skills/create-project-from-template/assets/icons/jetpack.svg +10 -0
- package/assets/skills/create-project-from-template/references/myapplication-checklist.md +92 -0
- package/assets/skills/create-room-storage/SKILL.md +39 -0
- package/assets/skills/create-shared-component/SKILL.md +160 -0
- package/assets/skills/create-signalr-channel/SKILL.md +39 -0
- package/assets/skills/create-usecase/SKILL.md +142 -0
- package/assets/skills/create-workmanager-task/SKILL.md +41 -0
- package/assets/skills/github-repo-settings/SKILL.md +152 -0
- package/assets/skills/interview-me/SKILL.md +150 -0
- package/dist/errors.js +49 -0
- package/dist/frontmatter.js +44 -0
- package/dist/github.js +23 -0
- package/dist/index.js +6 -0
- package/dist/server.js +131 -0
- package/dist/source/bundled.js +78 -0
- package/dist/source/cache.js +61 -0
- package/dist/source/github-source.js +68 -0
- package/dist/source/github.js +185 -0
- package/dist/source/index.js +36 -0
- package/dist/source/types.js +1 -0
- package/dist/validation.js +33 -0
- package/dist/version.js +26 -0
- package/package.json +38 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила Model в MVI
|
|
10
|
+
|
|
11
|
+
- Для данных, поддерживаемых Room, храни и передавай класс `Entity` из Room напрямую в классах
|
|
12
|
+
`Model` фичи и composable-компонентах; не маппи его в UI-модель или другой промежуточный класс.
|
|
13
|
+
- В классах `Model` фичи называй свойства Room entity по форме entity: используй `...Entity` для
|
|
14
|
+
одной entity и `...Entities` для списка entity.
|
|
15
|
+
- В классах `Model` MVI объявляй свойства, полностью производные от других свойств модели, с явным
|
|
16
|
+
типом и кастомным геттером на следующей строке с отступом; не инициализируй и не храни производное
|
|
17
|
+
значение в объявлении свойства.
|
|
18
|
+
- Для моделей экрана, поддерживаемых локальными коллекциями, избегай хранения или обновления
|
|
19
|
+
`isLoading`, когда загрузку можно вывести из состояния коллекции; считай экран загружающимся,
|
|
20
|
+
когда поддерживающая коллекция пуста.
|
|
21
|
+
- Не храни изменяемое булево состояние загрузки для сетевых запросов; храни nullable `Job` для
|
|
22
|
+
каждого запроса в `Model` и предоставляй загрузку как вычисляемое свойство, например
|
|
23
|
+
`val isLoading: Boolean get() = requestJob?.isActive == true`.
|
|
24
|
+
- При запуске сетевого запроса из ViewModel присваивай запущенный `Job` в `Model`, используй
|
|
25
|
+
`invokeOnCompletion`, чтобы сбросить этот job в `null`, и не устанавливай загрузку через булевы
|
|
26
|
+
обновления в `try/finally`.
|
|
27
|
+
- Для данных экрана, поддерживаемых Room и обновляемых из сети, используй отдельные intent-ы
|
|
28
|
+
`Collect...` и `Load...`: `Collect...` читает данные из Room, `Load...` выполняет сетевой запрос и
|
|
29
|
+
сохраняет результат в Room.
|
|
30
|
+
- При сборе данных entity из Room через `FlowUseCase` называй параметр лямбды `collectLatest` как
|
|
31
|
+
`entity` для одной entity или `entities` для списка entity.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила MVI
|
|
10
|
+
|
|
11
|
+
- Экраны фич находятся в `features/{feature}` и разбиты на `{Feature}Screen.kt`,
|
|
12
|
+
`{Feature}ViewModel.kt`, `model/{Feature}Model.kt`, `intent/{Feature}Intent.kt`, опционально
|
|
13
|
+
`event/{Feature}Event.kt` и опционально `navigation/{Feature}Route.kt`.
|
|
14
|
+
- При создании нового экрана сразу создавай его файлы `{Feature}ViewModel.kt`,
|
|
15
|
+
`model/{Feature}Model.kt` и `intent/{Feature}Intent.kt`, даже если начальное состояние экрана и
|
|
16
|
+
intent-ы минимальны; не создавай отдельные composable-экраны без соответствующих MVI-классов.
|
|
17
|
+
- Классы `ViewModel` используют `@HiltViewModel`, инъекцию через конструктор и наследуются от общего
|
|
18
|
+
базового MVI ViewModel проекта.
|
|
19
|
+
- Не размещай константы или функции-расширения в файлах/классах MVI `ViewModel`, `Screen`, `Intent`,
|
|
20
|
+
`Model`, `Event` или `Route`; переноси их в отдельные не-MVI файлы/пакеты.
|
|
21
|
+
- Не размещай вспомогательные классы внутри MVI-классов; объявляй их на уровне файла или в отдельных
|
|
22
|
+
файлах/пакетах.
|
|
23
|
+
- Не создавай и не храни изменяемые переменные в классах ViewModel; храни UI-состояние в классах
|
|
24
|
+
Model. Допускается публичный immutable `Flow<PagingData<T>>`, собранный из параметров Model и
|
|
25
|
+
закэшированный в lifecycle ViewModel; не дублируй его элементы или load state в Model.
|
|
26
|
+
- Размещай бизнес-логику, ветвление и решения экрана внутри соответствующей ветки intent в
|
|
27
|
+
`dispatch`; composable-экраны и компоненты должны получать уже подготовленное UI-состояние и
|
|
28
|
+
только диспатчить intent-ы.
|
|
29
|
+
- Класс `ViewModel` может объявлять только функции `dispatch` и `catch`; не объявляй в нём приватные
|
|
30
|
+
или публичные вспомогательные функции. Встраивай вычисления и преобразования в соответствующую
|
|
31
|
+
ветку `dispatch` либо выноси их из ViewModel в соответствующий архитектурный слой.
|
|
32
|
+
- `dispatch` — это `when` по всем веткам intent без `else`; состояние меняется только через
|
|
33
|
+
`reduce { it.copy(...) }`.
|
|
34
|
+
- Одноразовые действия используют `send({Feature}Event...)` из ViewModel и `ObserveAsEvents` на
|
|
35
|
+
экране.
|
|
36
|
+
- Типы `Intent`, `Model` и `Event` реализуют общие маркерные интерфейсы MVI проекта.
|
|
37
|
+
- ViewModel-и внедряют конкретные классы `UseCase` / `FlowUseCase`.
|
|
38
|
+
- Вызывай одноразовые use case внутри `launch { ... }` с `.getOrThrow()` и обрабатывай выброшенные
|
|
39
|
+
исключения в функции `catch` ViewModel.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила навигации
|
|
10
|
+
|
|
11
|
+
- Каждый маршрут экрана — это `@Serializable data class` или `@Serializable data object`,
|
|
12
|
+
реализующий `NavKey`.
|
|
13
|
+
- Маршруты находятся в `features/{feature}/navigation`.
|
|
14
|
+
- Используй `data object {Feature}Route: NavKey` для экранов без аргументов.
|
|
15
|
+
- Используй `data class {Feature}Route(...): NavKey` для экранов с аргументами.
|
|
16
|
+
- ViewModel-и получают аргументы маршрута через `savedStateHandle.toRoute<{Feature}Route>()`.
|
|
17
|
+
- Регистрируй маршруты в навигационной обёртке отображения проекта через
|
|
18
|
+
`entry<{Feature}Route> { {Feature}Screen(it) }`, когда у маршрута есть аргументы, или
|
|
19
|
+
`entry<{Feature}Route> { {Feature}Screen() }`, когда их нет.
|
|
20
|
+
- Для навигации назад используй отдельный `@Serializable data object BackRoute: NavKey`.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила сети
|
|
10
|
+
|
|
11
|
+
- Имена классов моделей запросов должны заканчиваться на `Request`.
|
|
12
|
+
- Имена классов моделей ответов должны заканчиваться на `Response`.
|
|
13
|
+
- Каждая модель запроса и ответа должна быть аннотирована `@Serializable`, а каждое поле должно
|
|
14
|
+
иметь аннотацию `@SerialName`.
|
|
15
|
+
- Называй сетевые use case по пути сетевого запроса или методу сетевого сервиса в PascalCase плюс
|
|
16
|
+
`UseCase`; например, `ktorHttpClient.get("items/details")` должен быть `ItemsDetailsUseCase`, а
|
|
17
|
+
`networkService.catalogBrandsFavorites(...)` — `CatalogBrandsFavoritesUseCase`. Не используй имена
|
|
18
|
+
в стиле intent, такие как `Load...UseCase`, для прямых синхронных сетевых вызовов.
|
|
19
|
+
- Называй кастомные сетевые исключения по тому же пути запроса или методу сетевого сервиса в
|
|
20
|
+
PascalCase плюс `Exception`; например, `ktorHttpClient.get("items/details")` должен использовать
|
|
21
|
+
`ItemsDetailsException`, а `networkService.catalogBrandsFavorites(...)` —
|
|
22
|
+
`CatalogBrandsFavoritesException`.
|
|
23
|
+
- Объявляй кастомные типы `data class` сетевых исключений внутри сетевого use case, который их
|
|
24
|
+
выбрасывает.
|
|
25
|
+
- В сетевых use case создавай `val request = ...` внутри лямбды `request = { ... }` непосредственно
|
|
26
|
+
перед вызовом `networkService`; никогда не создавай запрос вне этой лямбды и не встраивай его
|
|
27
|
+
создание в аргументы `networkService`.
|
|
28
|
+
- Используй `handleResponse` для сетевых вызовов, обрабатываемых через callback, и
|
|
29
|
+
`handleResponseResult(...).getOrThrow()`, когда сетевой ответ нужно потребить как значение внутри
|
|
30
|
+
`execute`; если failure нужно преобразовать в endpoint-specific exception, используй
|
|
31
|
+
`getOrElse`, пробрось `CancellationException` и выбрось конкретное исключение. Не оборачивай
|
|
32
|
+
результаты use case вручную в `Result.success` / `Result.failure`.
|
|
33
|
+
- При вызове `handleResponse` всегда передавай все три именованных аргумента: `request`, `onSuccess`
|
|
34
|
+
и `onFailure`; в `onFailure` создавай отдельный `data class`-исключение, наследующее базовое
|
|
35
|
+
сетевое исключение проекта, и выбрасывай его; перехватывай этот конкретный тип исключения в
|
|
36
|
+
функции `catch` ViewModel.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила Preview
|
|
10
|
+
|
|
11
|
+
- Всегда аннотируй previews через `@PreviewWrapper(ThemeWrapper::class)`.
|
|
12
|
+
- Preview должны вызывать приватный composable `*Content`, а не публичный composable `*Screen`,
|
|
13
|
+
принимающий ViewModel.
|
|
14
|
+
- Composable-компоненты должны иметь только одну функцию preview; все варианты preview представляй
|
|
15
|
+
через `PreviewParameterProvider`.
|
|
16
|
+
- Когда у компонента или экрана есть несколько значимых визуальных состояний, используй
|
|
17
|
+
`PreviewParameterProvider` и одну функцию preview с `@PreviewParameter`; не пиши несколько
|
|
18
|
+
отдельных функций preview для разных состояний.
|
|
19
|
+
- Когда компонент использует `{Component}State` data class, потому что у него больше одного поля,
|
|
20
|
+
всегда создавай соответствующий приватный `PreviewParameterProvider` для этого состояния в том же
|
|
21
|
+
файле.
|
|
22
|
+
- Когда компонент условно рендерит части своего UI на основе вычисляемых свойств `{Component}State`,
|
|
23
|
+
соответствующий `PreviewParameterProvider` должен включать одно значение на каждую значимую
|
|
24
|
+
комбинацию видимости, чтобы каждая условная ветка была представлена в preview, а не только
|
|
25
|
+
состояние по умолчанию/первое.
|
|
26
|
+
- Используй `BooleanProvider` для preview с булевыми параметрами.
|
|
27
|
+
- Классы `PreviewParameterProvider` приватные и объявляются в конце файла.
|
|
28
|
+
- Строй значения preview и `PreviewParameterProvider` через `Empty.copy(...)`, когда модель
|
|
29
|
+
предоставляет экземпляр `Empty`; не создавай их полным явным вызовом конструктора.
|
|
30
|
+
- В вызовах `Empty.copy(...)` для preview устанавливай только те поля, которые компонент
|
|
31
|
+
действительно читает/рендерит; не добавляй несвязанные поля «для реалистичности».
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила realtime-соединений
|
|
10
|
+
|
|
11
|
+
- Создавай отдельный `RealtimeDataSource` для каждого hub/канала предметной области. Общие
|
|
12
|
+
создание соединения, аутентификацию, retry/backoff, логирование и закрытие держи в общей
|
|
13
|
+
инфраструктуре соединения.
|
|
14
|
+
- Регистрируй обработчики входящих событий до запуска соединения и команды начальной подписки —
|
|
15
|
+
только после успешного подключения.
|
|
16
|
+
- Публикуй conflatable invalidation-сигналы через приватный `MutableSharedFlow` и публичный
|
|
17
|
+
`SharedFlow`: потеря нескольких одинаковых сигналов допустима только когда следующий reload
|
|
18
|
+
получает полное authoritative state. Для каждого обязательного payload-события используй
|
|
19
|
+
lossless `Channel`/очередь либо сразу durable Room-запись; не полагайся на `SharedFlow(replay = 0)`
|
|
20
|
+
при отсутствии или отставании подписчика.
|
|
21
|
+
- Считай realtime-событие сигналом обновить источник истины: вызывай обычный use case, который
|
|
22
|
+
обновляет Room, а UI продолжает наблюдать Room. Передавай payload напрямую в UI только для
|
|
23
|
+
действительно эфемерных событий, которым не требуется согласованное сохранённое состояние.
|
|
24
|
+
- Владение жизненным циклом всех realtime-каналов держи в одном `StartRealtimeUseCase` или
|
|
25
|
+
эквивалентном session-scoped use case. DataSource отдельного hub не должен самостоятельно владеть
|
|
26
|
+
сессией приложения.
|
|
27
|
+
- Активируй collectors событий до запуска соединения и initial subscription. Используй принятый в
|
|
28
|
+
проекте undispatched-start либо выполни гарантированный initial reload после подключения, чтобы
|
|
29
|
+
не потерять первое событие из `onConnected`.
|
|
30
|
+
- Перезапускай набор соединений при смене авторизованной сессии через отменяемый поток наподобие
|
|
31
|
+
`collectLatest`; после logout закрывай соединения и не продолжай retry со старым токеном.
|
|
32
|
+
- Retry-цикл действует только пока активна текущая сессия. Не перехватывай `CancellationException`,
|
|
33
|
+
а прочие ошибки логируй без токенов и персональных payload и повторяй с ограниченной задержкой или
|
|
34
|
+
backoff согласно инфраструктуре проекта.
|
|
35
|
+
- Создавай новое соединение на новую попытку, если клиент нельзя безопасно перезапустить. Всегда
|
|
36
|
+
снимай обработчики и закрывай соединение в `finally`; cleanup выполняй в `NonCancellable` с
|
|
37
|
+
ограниченным timeout, чтобы остановка сессии не зависла.
|
|
38
|
+
- Не запускай несколько конкурирующих соединений одного hub для одной сессии, если это не является
|
|
39
|
+
явным требованием продукта.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила ресурсов
|
|
10
|
+
|
|
11
|
+
- Строки UI используются через фасад строк проекта, а не через прямое использование `R.string` или
|
|
12
|
+
`R.plurals` в UI-коде.
|
|
13
|
+
- Не хардкодь пользовательские строки напрямую в Kotlin или XML UI-коде; добавляй их в `strings.xml`
|
|
14
|
+
и используй через фасад строк проекта.
|
|
15
|
+
- При добавлении строкового ресурса добавляй его в `strings.xml` и предоставляй доступ через фасад
|
|
16
|
+
строк проекта.
|
|
17
|
+
- Для UI-текста в верхнем регистре добавляй отдельный ресурс в верхнем регистре и запись в фасаде
|
|
18
|
+
строк; не вызывай `uppercase()` или `uppercase(Locale...)` в UI, модели или коде маппера.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила Room
|
|
10
|
+
|
|
11
|
+
- В интерфейсах DAO размещай все обычные методы `fun` перед любыми методами `suspend fun`.
|
|
12
|
+
- При изменении таблиц или entity базы данных Room всегда увеличивай `AppDatabase.DATABASE_VERSION`.
|
|
13
|
+
- Для entity Room объявляй первичные ключи в параметре `@Entity(primaryKeys = [...])` вместо
|
|
14
|
+
использования `@PrimaryKey` на свойстве.
|
|
15
|
+
- В интерфейсах `@Dao` помечай методы, возвращающие типы Pojo, аннотацией `@Transaction`, чтобы
|
|
16
|
+
обеспечить согласованное чтение из нескольких таблиц.
|
|
17
|
+
- Используй `AppDatabase.withTransaction` только когда блок транзакции содержит два и более вызова
|
|
18
|
+
методов DAO/базы данных; не оборачивай один вызов метода в `withTransaction`.
|
|
19
|
+
- Для чтения одной строки Room предоставляй как nullable `select`, так и non-null `selectNotNull`
|
|
20
|
+
форматы, когда оба варианта использования существуют:
|
|
21
|
+
```kotlin
|
|
22
|
+
@Query("SELECT * FROM EntityTable WHERE id = :id LIMIT 1")
|
|
23
|
+
suspend fun select(id: Int): Entity?
|
|
24
|
+
|
|
25
|
+
@Query("SELECT * FROM EntityTable WHERE id = :id LIMIT 1")
|
|
26
|
+
suspend fun selectNotNull(id: Int): Entity
|
|
27
|
+
```
|
|
28
|
+
- Используй `select`, когда строка может отсутствовать; используй `selectNotNull` только когда
|
|
29
|
+
вызывающий код точно знает, что значение существует в базе данных.
|
|
30
|
+
- Не пиши методы DAO с `@Transaction`, которые оркестрируют несколько других методов DAO, например
|
|
31
|
+
метод `replace`, вызывающий `delete()`, а затем `upsert(entity)`; держи методы DAO как отдельные
|
|
32
|
+
декларативные операции `@Query`/`@Insert`/`@Upsert`/`@Delete` и комбинируй несколько вызовов DAO
|
|
33
|
+
через `AppDatabase.withTransaction` на уровне use case.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила Scaffold
|
|
10
|
+
|
|
11
|
+
- Snackbar для обычных сообщений размещай внутри `snackbarHost` в `Scaffold`; snackbar, которые
|
|
12
|
+
должны появляться над статус-баром (верхние баннеры ошибок), размещай вне `Scaffold` в
|
|
13
|
+
оборачивающем `Box`, выровненном по `Alignment.TopCenter`, с `Modifier.statusBarsPadding()`.
|
|
14
|
+
- Рендери содержимое обычного snackbar через `SnackbarMessage`; содержимое snackbar ошибки — через
|
|
15
|
+
`SnackbarErrorMessage`.
|
|
16
|
+
- Когда два snackbar сосуществуют внутри `snackbarHost`, оборачивай их в `Box` и используй отдельные
|
|
17
|
+
экземпляры `SnackbarHostState` с разными значениями `containerColor`.
|
|
18
|
+
- FAB всегда использует `floatingActionButtonPosition = FabPosition.Center`; применяй горизонтальный
|
|
19
|
+
padding через `Modifier.padding(horizontal = 16.dp)` на самой кнопке.
|
|
20
|
+
- Когда экрану нужна статичная кнопка действия, закреплённая внизу, размещай её в слоте
|
|
21
|
+
`floatingActionButton` в `Scaffold`, а не в `bottomBar`.
|
|
22
|
+
- Закрывай текущий snackbar перед показом нового: вызывай `hostState.currentSnackbarData?.dismiss()`
|
|
23
|
+
перед `scope.launch { hostState.showSnackbar(...) }`.
|
|
24
|
+
- Всегда оборачивай `Scaffold` в многострочно оформленный `Box` с
|
|
25
|
+
`modifier = Modifier.fillMaxSize()`, когда верхний snackbar нужно разместить вне scaffold.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила Shimmer / Loading Placeholder
|
|
10
|
+
|
|
11
|
+
- Используй
|
|
12
|
+
`Modifier.placeholder(visible = ..., highlight = PlaceholderHighlight.shimmer(), color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = RoundedCornerShape(...))`
|
|
13
|
+
для skeleton-загрузки.
|
|
14
|
+
- Используй `Spacer` для отдельного placeholder без дочернего содержимого; не используй пустой `Box`
|
|
15
|
+
только для рендеринга placeholder. Используй `Box` только когда placeholder также должен служить
|
|
16
|
+
контейнером для дочерних composable.
|
|
17
|
+
- Всегда передавай `visible = state.isLoading` (или соответствующий boolean) — не хардкодь
|
|
18
|
+
`visible = true`, кроме как внутри выделенных loading-composable.
|
|
19
|
+
- Используй `MaterialTheme.colorScheme.surfaceContainerHigh` как цвет placeholder; не используй
|
|
20
|
+
сырые константы `Color`.
|
|
21
|
+
- Для кнопки действия или кнопки в слоте `floatingActionButton` держи реальную кнопку в composition
|
|
22
|
+
во время загрузки и применяй
|
|
23
|
+
`Modifier.placeholder(visible = state.isLoading, highlight = PlaceholderHighlight.shimmer(), color = MaterialTheme.colorScheme.surfaceVariant, shape = RoundedCornerShape(...))`
|
|
24
|
+
прямо к её modifier; не заменяй кнопку условным placeholder `Spacer`.
|
|
25
|
+
- Согласуй `shape` placeholder с формой реального содержимого, которое он представляет (например,
|
|
26
|
+
`RoundedCornerShape(16.dp)` для карточек, `RoundedCornerShape(8.dp)` для меньших элементов).
|
|
27
|
+
- Для полноэкранных или секционных состояний загрузки создавай выделенный loading-composable
|
|
28
|
+
(например, `PageLoading`, `SectionLoading`), использующий `SharedLazyColumn` с
|
|
29
|
+
`userScrollEnabled = false` и элементы `Spacer`, оформленные через
|
|
30
|
+
`.placeholder(visible = true, ...)`.
|
|
31
|
+
- В выделенных loading-composable `visible` всегда `true`; вызывающий код решает, когда показывать
|
|
32
|
+
composable.
|
|
33
|
+
- Импортируй `PlaceholderHighlight` и `placeholder`/`shimmer` из общего UI-модуля проекта, а не
|
|
34
|
+
напрямую из сторонней библиотеки.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила TextField
|
|
10
|
+
|
|
11
|
+
- Для `TextField`, являющихся полями поиска, храни значение как `TextFieldValue`, а не как `String`,
|
|
12
|
+
и всегда явно устанавливай `selection`, чтобы курсор находился в конце текста, а не в начале:
|
|
13
|
+
```kotlin
|
|
14
|
+
var textFieldValue by remember {
|
|
15
|
+
mutableStateOf(
|
|
16
|
+
TextFieldValue(
|
|
17
|
+
text = state.query,
|
|
18
|
+
selection = TextRange(index = state.query.length)
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
- При программном обновлении текста поля поиска (например, из state после ответа сервера или сброса
|
|
24
|
+
фильтра) пересоздавай `TextFieldValue` с `selection = TextRange(index = newText.length)`, чтобы
|
|
25
|
+
курсор не сбрасывался в начало строки.
|
|
26
|
+
- Когда на экране несколько `TextField` расположены последовательно друг за другом, у всех полей,
|
|
27
|
+
кроме последнего, устанавливай `keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)` и
|
|
28
|
+
переводи фокус на следующее поле через `keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) })`.
|
|
29
|
+
- У последнего `TextField` в такой последовательности устанавливай
|
|
30
|
+
`keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)` и скрывай клавиатуру через
|
|
31
|
+
`keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() })`.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила типографики
|
|
10
|
+
|
|
11
|
+
- Всегда устанавливай стиль текста через аргумент `style` в `Text` или `SharedFixedText`; никогда
|
|
12
|
+
не используй отдельные аргументы `fontSize`, `fontWeight` или `color`.
|
|
13
|
+
- Используй расширения `MaterialTheme.typography.<token>` как базовый стиль, затем применяй
|
|
14
|
+
переопределения через
|
|
15
|
+
`.copy(color = ..., lineHeight = ..., letterSpacing = ..., textAlign = ...)`.
|
|
16
|
+
- В `Text` держи визуальные поля стиля текста внутри `style`: `color`, `lineHeight`, `letterSpacing`
|
|
17
|
+
и `textAlign`; не передавай их как отдельные аргументы `Text`.
|
|
18
|
+
- Не выделяй `MaterialTheme.typography...copy(...)` в локальные переменные вроде
|
|
19
|
+
`val fieldTextStyle`; передавай выражение стиля напрямую в composable, даже если оно дублирует
|
|
20
|
+
соседний код стиля.
|
|
21
|
+
- Не создавай `TextStyle(...)` напрямую внутри composable; всегда начинай с токена
|
|
22
|
+
`MaterialTheme.typography`.
|
|
23
|
+
- Доступные токены следуют паттерну `regular<size>` (насыщенность 400) и `medium<size>`
|
|
24
|
+
(насыщенность 500): `regular12`, `regular14`, `regular15`, `regular16`, `regular18`, `regular22`,
|
|
25
|
+
`medium11`, `medium12`, `medium14`, `medium16`, `medium17`, `medium22`. Используй токен, размер и
|
|
26
|
+
насыщенность которого соответствуют дизайну; не создавай ad-hoc экземпляры `TextStyle`,
|
|
27
|
+
приближающие существующий токен.
|
|
28
|
+
- Для `textAlign` передавай его внутри `.copy(textAlign = ...)` на стиле, а не как отдельный
|
|
29
|
+
аргумент `textAlign` в `Text`.
|
|
30
|
+
- Для span-ов `AnnotatedString` используй `MaterialTheme.typography.spanRegular14` или
|
|
31
|
+
`spanMedium14` как базу `SpanStyle`.
|
|
32
|
+
- Не хардкодь цвета внутри `TextStyle`; всегда ссылайся на `MaterialTheme.colorScheme.*`.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила UseCase
|
|
10
|
+
|
|
11
|
+
- Размещай операции Room, Ktor, DataStore и бизнес-операции в конкретных классах `UseCase` /
|
|
12
|
+
`FlowUseCase` в `shared/domain/usecase`; не создавай слои Repository или Interactor для новой
|
|
13
|
+
работы.
|
|
14
|
+
- Используй `UseCase<P, R>` для одноразовых suspend-операций и `FlowUseCase<P, R>` для наблюдаемых
|
|
15
|
+
потоков.
|
|
16
|
+
- Внедряй конкретные зависимости данных напрямую в конструктор use case: `NetworkService`,
|
|
17
|
+
`AppDatabase`, классы DAO, классы DataStore, хелперы аналитики или другие use case при композиции
|
|
18
|
+
операций.
|
|
19
|
+
- Внедряй `SharedDispatchers` как не-`private` параметр конструктора и передавай `dispatchers.io`
|
|
20
|
+
для работы с Room/Ktor или `dispatchers.immediate` только для лёгкой по CPU немедленной работы.
|
|
21
|
+
- Не оборачивай `execute` в `withContext` или `flowOn`; базовый класс `UseCase` / `FlowUseCase` сам
|
|
22
|
+
отвечает за переключение диспетчеров.
|
|
23
|
+
- `UseCase.execute` возвращает сырой domain-результат `R` и выбрасывает domain-специфичные
|
|
24
|
+
исключения при ошибках; он не должен возвращать `Result`, `Result.success` или `Result.failure`.
|
|
25
|
+
- Вызывай другие экземпляры `UseCase` из `execute` с `.getOrThrow()`, чтобы ошибки распространялись
|
|
26
|
+
в результат родительского `UseCase`.
|
|
27
|
+
- Вызывай экземпляры `UseCase` из ViewModel внутри `launch { ... }` и завершай каждый вызов через
|
|
28
|
+
`.getOrThrow()`; обрабатывай выброшенные domain-, Room- и сетевые исключения в функции `catch`
|
|
29
|
+
ViewModel.
|
|
30
|
+
- `FlowUseCase.execute` напрямую возвращает `Flow<R>`, не является `suspend` и не должен оборачивать
|
|
31
|
+
значения в `Result`.
|
|
32
|
+
- Называй классы `FlowUseCase` по типу значения, которое они возвращают, плюс `FlowUseCase`;
|
|
33
|
+
например, `Flow<List<ItemEntity>>` должен быть `ItemEntitiesFlowUseCase`, а `Flow<ItemEntity>` —
|
|
34
|
+
`ItemEntityFlowUseCase`.
|
|
35
|
+
- При отсутствии входных параметров используй `Unit` как тип параметра и оставляй
|
|
36
|
+
`execute(params: Unit)`.
|
|
37
|
+
- Для ровно одного входного параметра используй domain-тип как `P`, переименуй override-параметр в
|
|
38
|
+
семантическое имя и добавь `@file:Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")`; не создавай
|
|
39
|
+
`Params` data class с одним полем, даже если поле nullable или имеет значение по умолчанию.
|
|
40
|
+
- Для двух и более входных параметров определяй вложенный `data class Params(...)` внутри use case и
|
|
41
|
+
используй `UseCase<FeatureUseCase.Params, R>` или `FlowUseCase<FeatureFlowUseCase.Params, R>`; не
|
|
42
|
+
используй `Pair`, `Triple`, map-ы или несколько аргументов `invoke`.
|
|
43
|
+
- Если у use case есть вложенный класс `Params`, импортируй его напрямую
|
|
44
|
+
(`import package.SomeUseCase.Params`) и используй только короткое имя в generic-сигнатуре:
|
|
45
|
+
`UseCase<Params, Result>` или `FlowUseCase<Params, Result>`; не используй квалифицированную форму
|
|
46
|
+
`SomeUseCase.Params`.
|
|
47
|
+
- Если у use case есть вложенный выходной/результирующий data class (например,
|
|
48
|
+
`data class PaymentData` внутри `PaymentDataFlowUseCase`), импортируй его напрямую
|
|
49
|
+
(`import package.SomeUseCase.PaymentData`) и используй только короткое имя везде, включая
|
|
50
|
+
generic-сигнатуру: `FlowUseCase<Params, PaymentData>`; не используй квалифицированную форму
|
|
51
|
+
`SomeUseCase.PaymentData`.
|
|
52
|
+
- Обычный `FlowUseCase`, наблюдающий Room, должен оборачивать один flow-метод DAO; если разным
|
|
53
|
+
фильтрам нужны разные flow-методы DAO, создавай отдельные классы вместо ветвления внутри одного
|
|
54
|
+
use case. Paging-`FlowUseCase` может строить `Pager(...).flow` из `PagingSource` и опционального
|
|
55
|
+
`RemoteMediator`; не применяй к нему ограничение одного DAO Flow.
|
|
56
|
+
- Новые use case — это конкретные классы с `@Inject constructor`, и обычно им не нужны модули Hilt
|
|
57
|
+
`@Binds`.
|
|
58
|
+
- Константы, относящиеся к логике конкретного use case (лимиты, ключи, таймауты, пути и т. п.),
|
|
59
|
+
размещай в `companion object` этого use case; не выноси их в отдельные config-файлы или
|
|
60
|
+
общие файлы констант, если они используются только этим use case.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Правила рабочего процесса
|
|
2
|
+
|
|
3
|
+
- Используй стандартные соглашения проекта и следуй существующей документации при внесении
|
|
4
|
+
изменений.
|
|
5
|
+
- Не бойся дублирования: дублирование предпочтительнее преждевременных абстракций, вспомогательных
|
|
6
|
+
переменных, вспомогательных функций или общих моделей, которые лишь устраняют небольшой
|
|
7
|
+
повторяющийся код.
|
|
8
|
+
- Реализацию пишет профильный саб-агент, а не главная сессия. По умолчанию делегируй: закрытие одной
|
|
9
|
+
задачи трекера, багфикс с тестом, многофайловую правку — отдавай профильному агенту (kotlin-engineer,
|
|
10
|
+
compose-builder и т.д.). Главная сессия ставит задачу, фиксирует критерий готовности и принимает
|
|
11
|
+
результат, а не пишет реализацию сама.
|
|
12
|
+
- Крупные многошаговые задачи (затрагивающие много файлов, требующие многократных прогонов
|
|
13
|
+
сборки/тестов или способные оставить рабочее дерево в нерабочем состоянии при сбое) выполняй через
|
|
14
|
+
фонового агента в изолированном git worktree, а не напрямую в основной рабочей директории.
|
|
15
|
+
- После завершения такого фонового агента переноси его изменения из worktree в основную рабочую
|
|
16
|
+
директорию как незакоммиченные изменения, без автоматического коммита, и перепроверяй их
|
|
17
|
+
(валидация, тесты, сборка) уже в основной директории перед тем как предлагать коммит.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.gradle.kts"
|
|
4
|
+
- "**/*.gradle"
|
|
5
|
+
- "**/AndroidManifest.xml"
|
|
6
|
+
- "**/*.kt"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Правила WorkManager
|
|
10
|
+
|
|
11
|
+
- Реализуй фоновую задачу как тонкий `CoroutineWorker`: worker читает и проверяет входные данные,
|
|
12
|
+
вызывает существующий бизнес-`UseCase` через `.getOrThrow()` и преобразует итог в
|
|
13
|
+
`Result.success()`, `Result.retry()` или `Result.failure()`. Для чисто Android-системной операции,
|
|
14
|
+
например показа локального уведомления, worker может вызвать системный API напрямую, но не должен
|
|
15
|
+
содержать бизнес-решения фичи.
|
|
16
|
+
- Планирование и отмену работы размещай в отдельных `UseCase` в `shared/domain/usecase`; не
|
|
17
|
+
запускай новую работу из `companion object` worker и не передавай `WorkManager` во ViewModel.
|
|
18
|
+
- Для внедрения зависимостей следуй существующей Hilt-конвенции проекта: используй `@HiltWorker` и
|
|
19
|
+
`@AssistedInject`, когда настроен `HiltWorkerFactory`, либо существующий Hilt `EntryPoint`, когда
|
|
20
|
+
проект получает зависимости worker таким способом. Не создавай параллельную фабрику.
|
|
21
|
+
- Выбирай `enqueueUniqueWork` / `enqueueUniquePeriodicWork`, только если задача имеет естественный
|
|
22
|
+
стабильный identity. Имя unique work и теги должны быть стабильными; выбирай
|
|
23
|
+
`ExistingWorkPolicy` или `ExistingPeriodicWorkPolicy` по продуктовой семантике сохранения, замены
|
|
24
|
+
или продолжения существующей работы, а не по удобству реализации.
|
|
25
|
+
- Передавай через `Data` только небольшой набор примитивов и строк. Храни крупный payload и
|
|
26
|
+
состояние повторяемой операции в Room, а в worker передавай стабильный идентификатор записи.
|
|
27
|
+
- Объявляй только необходимые `Constraints`. Не требуй сеть для локальной работы и не добавляй
|
|
28
|
+
initial delay, backoff или periodic execution без требования сценария.
|
|
29
|
+
- Настраивай backoff для действительно повторяемых ошибок. Ограничивай собственные retry по
|
|
30
|
+
`runAttemptCount`, если бесконечные повторы не являются явной частью продукта.
|
|
31
|
+
- Возвращай `success` после завершённой или идемпотентно уже выполненной операции, `retry` только для
|
|
32
|
+
временной ошибки, а `failure` — для некорректного input и постоянной ошибки. Не превращай любую
|
|
33
|
+
ошибку в `retry`.
|
|
34
|
+
- Никогда не поглощай `CancellationException` и не преобразуй отмену worker в `retry` или
|
|
35
|
+
`failure`; освобождай ресурсы и пробрасывай отмену дальше.
|
|
36
|
+
- Делай повторяемую операцию идемпотентной или защищай её серверным/локальным operation id, потому
|
|
37
|
+
что WorkManager гарантирует выполнение, но не exactly-once семантику бизнес-операции.
|
|
38
|
+
- Для отмены используй тот же unique work name или стабильный tag/id, который задаёт scheduling
|
|
39
|
+
use case. Не дублируй строковые ключи между worker и use case: держи относящиеся к задаче
|
|
40
|
+
константы рядом с владельцем scheduling-контракта.
|
|
41
|
+
- Для account/session-scoped работы включай identity владельца или generation сессии в unique work
|
|
42
|
+
и input. Отмена при logout является best effort: worker обязан повторно проверить актуальную
|
|
43
|
+
сессию перед сетевым вызовом и перед записью результата в Room, чтобы старая работа не записала
|
|
44
|
+
данные после logout или смены пользователя.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-data-layer
|
|
3
|
+
description: >-
|
|
4
|
+
Use when пользователь просит собрать или расширить составной поток данных фичи между Ktor, Room,
|
|
5
|
+
мапперами, use case, realtime или WorkManager — например "wire up API and Room", "add a data
|
|
6
|
+
layer", "reload persisted data from a socket event" или "sync in background". Выбирает
|
|
7
|
+
минимальный поток и маршрутизирует работу к специализированным skills. Не используй для одного
|
|
8
|
+
уже определённого endpoint, DAO/entity, mapper, use case, worker или realtime-канала; используй
|
|
9
|
+
соответствующий атомарный skill напрямую. Не используй для Screen/ViewModel.
|
|
10
|
+
metadata:
|
|
11
|
+
author: michaelbel
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Составной поток данных
|
|
15
|
+
|
|
16
|
+
Собери только те части data/domain-потока, которые нужны пользовательскому сценарию. Этот skill
|
|
17
|
+
координирует специализированные skills и не дублирует их шаблоны.
|
|
18
|
+
|
|
19
|
+
## Сначала определи границы
|
|
20
|
+
|
|
21
|
+
1. Изучи аналогичные потоки и существующие типы в целевом проекте.
|
|
22
|
+
2. Зафиксируй источник истины: ответ текущей операции, Room или другой уже существующий store.
|
|
23
|
+
3. Определи, что запускает обновление: UI, realtime-событие или WorkManager.
|
|
24
|
+
4. Отметь уже реализованные части и не пересоздавай их.
|
|
25
|
+
5. Выбери минимальный поток из таблицы ниже. Если сценарий гибридный, объедини только необходимые
|
|
26
|
+
строки.
|
|
27
|
+
|
|
28
|
+
| Сценарий | Подключаемые skills | Результат |
|
|
29
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
30
|
+
| Ktor-only | `create-ktor-endpoint`, затем обязательный `create-usecase` | Ответ используется конкретной domain-операцией без искусственного Room-кэша |
|
|
31
|
+
| Room-only | `create-room-storage`, затем `create-usecase`; `create-domain-mapper` только при реальной границе моделей | Чтение/запись локального источника без сетевого слоя |
|
|
32
|
+
| Ktor → mapper → Room | `create-ktor-endpoint`, `create-domain-mapper`, `create-room-storage`, `create-usecase` | Load-use case получает API-модель, маппит и атомарно обновляет Room; UI наблюдает Room отдельным FlowUseCase |
|
|
33
|
+
| realtime → reload | `create-signalr-channel`, затем `create-usecase`; добавь Ktor/mapper/Room skills только для отсутствующих частей reload-потока | Realtime-событие служит сигналом обновления, а не вторым источником состояния |
|
|
34
|
+
| WorkManager → network → Room | `create-workmanager-task`, `create-ktor-endpoint`, `create-domain-mapper`, `create-room-storage`, `create-usecase` только для отсутствующих частей | Worker вызывает переиспользуемую business-операцию; network/Room-логику не дублирует |
|
|
35
|
+
|
|
36
|
+
Перед реализацией загрузи каждый выбранный skill и следуй его границам. Порядок в таблице описывает
|
|
37
|
+
зависимости, но не требует пересоздавать уже готовые нижние слои.
|
|
38
|
+
|
|
39
|
+
## Инварианты композиции
|
|
40
|
+
|
|
41
|
+
- Не добавляй Repository или Interactor, если целевой проект связывает конкретные зависимости через
|
|
42
|
+
`UseCase` / `FlowUseCase`.
|
|
43
|
+
- Не создавай Room, mapper, request/response или фоновую задачу «для полноты». У каждого слоя должен
|
|
44
|
+
быть вызывающий сценарий.
|
|
45
|
+
- При Room как источнике истины разделяй обновление и наблюдение: одноразовый load use case пишет в
|
|
46
|
+
Room, отдельный `FlowUseCase` возвращает DAO Flow.
|
|
47
|
+
- Realtime handler и Worker инициируют use case. Они не копируют HTTP-вызовы, маппинг и SQL-запись
|
|
48
|
+
внутрь инфраструктурного класса.
|
|
49
|
+
- Композиционный use case разворачивает результат другого use case через `.getOrThrow()`.
|
|
50
|
+
- Используй транзакцию Room для согласованного набора связанных изменений, а не для одиночного
|
|
51
|
+
вызова DAO.
|
|
52
|
+
- Следуй структуре, именованию и уже выбранным библиотекам целевого проекта. Reference-проект
|
|
53
|
+
показывает поведение, но не является шаблоном для механической замены package name.
|
|
54
|
+
|
|
55
|
+
## Завершение
|
|
56
|
+
|
|
57
|
+
Проверь, что созданные части образуют один вызываемый вертикальный поток, зарегистрированы в
|
|
58
|
+
существующих database/network/realtime/worker точках расширения и не оставили неиспользуемых
|
|
59
|
+
моделей. Запусти релевантные тесты и сборку затронутых модулей. Экран подключай отдельно через
|
|
60
|
+
`create-feature-scaffold-screen`.
|