feat(activate): Phase C Wave 1 — end-to-end Store5 migration - #2686
Conversation
Phase C scaffolding seeded ahead of Wave 1 implementation work.
core/data:
- build.gradle.kts: api(projects.coreBase.store) -> api(projects.core.store)
Routes the framework primitives through the core/store seam, matching
the dependency-direction contract enforced for the rest of Phase C.
- src/commonMain/.../store/SubmitTypes.kt (NEW): typealias SubmitHandler,
SubmitState; CoroutineScope.submitHandler() delegating extension.
Feature modules import com.mifos.core.data.store.* instead of reaching
into template.core.base.store.submit.*.
core/ui:
- build.gradle.kts: add api(projects.coreBase.ui) so the typealias chain
resolves transitively for feature modules depending only on core/ui.
- src/commonMain/.../store/ViewModelTypes.kt (NEW): typealias
BaseViewModel<S, E, A>. Feature VMs import com.mifos.core.ui.store.*
instead of template.core.base.ui.viewmodel.*.
Composables (MutationScreenContent / ScreenContent / PagingScreenContent
/ LocalScreenStateDefaults) are NOT re-exported because Kotlin typealias
doesn't apply to Composables or CompositionLocals. Feature Screens import
them directly from template.core.base.ui.* via the api dependency chain
(feature -> core/ui -> core-base/ui). That dependency direction still
satisfies the architectural contract — no feature gradle has a direct
coreBase.* implementation() dep.
First end-to-end feature wave (network -> data -> domain -> feature).
Establishes the migration template that Waves 2-7 replicate.
core/network (per-resource API rewrite):
+ client/api/ClientApi.kt — suspend Ktorfit, POST clients/{id}?command=activate
+ center/api/CenterApi.kt — POST centers/{id}?command=activate
+ group/api/GroupApi.kt — POST groups/{id}?command=activate
Each interface is per-resource (not per-action); future client/center/
group feature waves add more methods to the same files.
di/NetworkModule.kt: + single<ClientApi/CenterApi/GroupApi> factories
Method-level @deprecated on legacy DataManagerClient.activateClient,
DataManagerCenter.activateCenter, DataManagerGroups.activateGroup,
ClientService.activateClient, CenterService.activateCenter,
GroupService.activateGroup (those files have other live methods, so
file-level deprecation isn't appropriate — granular method deprecation).
core/data (relocate + rewrite):
git mv core/data/repository/ActivateRepository.kt
-> core/data/activate/ActivateRepository.kt
git mv core/data/repositoryImp/ActivateRepositoryImp.kt
-> core/data/activate/impl/ActivateRepositoryImpl.kt (renamed)
ActivateRepository interface signature simplified: drops nullable
ActivatePayload? (always required); activateGroup() drops HttpResponse
return (Repository throws on non-2xx).
ActivateRepositoryImpl now injects (ClientApi, CenterApi, GroupApi)
directly; no DataManager dependency.
core/domain (delete pure-delegator use cases):
- core/domain/useCases/ActivateClientUseCase.kt (deleted)
- core/domain/useCases/ActivateCenterUseCase.kt (deleted)
- core/domain/useCases/ActivateGroupUseCase.kt (deleted)
- di/UseCaseModule.kt: drop 3 factoryOf(::ActivateXxxUseCase) entries
+ their imports.
feature/activate ({ui, di, navigation}/ restructure):
Layout was flat (ActivateViewModel.kt, ActivateScreen.kt,
ActivateUiState.kt, ActivateNavigation.kt all at root). Now:
ui/
ActivateState.kt — MVI state data class with TargetType enum
+ success/failure StringResource lookups
ActivateAction.kt — sealed interface ActivateClient/Center/Group
ActivateViewModel.kt — extends BaseViewModel<State, Nothing, Action>
ActivateScreen.kt — uses SubmitProgressOverlay + SubmitResultHandler
from template.core.base.ui.submit (via api chain
core/ui -> core-base/ui)
di/
ActivateModule.kt — single<ActivateRepositoryImpl> bind ActivateRepository
+ viewModelOf(::ActivateViewModel)
navigation/
ActivateRoute.kt — @serializable data class (split from Navigation)
ActivateNavigation.kt — NavGraphBuilder.activateDestination +
NavController.navigateToActivateRoute
VM no longer collects Flow<DataState>: SubmitHandler<Unit> drives the
Submitting -> Submitted | Failed lifecycle. Screen-side terminal
effects via SubmitResultHandler. Disabled-while-submitting UX via
enabled = !isSubmitting on the activate button (idempotent
double-tap protection at the framework layer too).
feature/activate/build.gradle.kts:
- implementation(projects.core.domain)
+ implementation(projects.core.data)
+ implementation(projects.core.ui)
No direct coreBase.* deps. Template Composables imported via api chain.
…+ restore Composable Previews - core/data/.../di/RepositoryModule.kt: imports updated to the new com.mifos.core.data.activate package; singleOf(::ActivateRepositoryImp) -> singleOf(::ActivateRepositoryImpl) bind ActivateRepository::class - feature/activate/.../di/ActivateModule.kt: reverted to viewModelOf-only. Repository binding belongs in core/data RepositoryModule per the Koin DI ownership boundary documented in C-features.md. - feature/activate/.../ui/ActivateScreen.kt: restored @Preview using Compose Multiplatform's org.jetbrains.compose.ui.tooling.preview.* (NOT androidx.compose.ui.tooling.preview.*). PreviewParameterProvider emits 2 variants (idle, submitting) — minimal coverage; future iteration can add Success / Failure dialog states.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ActivateScreen used 'Success' and 'Error' as raw String dialogTitles on MifosAlertDialog. Per the no-hardcoded-user-facing-strings rule (just added to C-features.md), every user-facing string in feature/* must come from composeResources. feature/activate/.../composeResources/values/strings.xml: + feature_activate_dialog_title_success: 'Success' + feature_activate_dialog_title_error: 'Error' feature/activate/.../ui/ActivateScreen.kt: - dialogTitle = "Success" + dialogTitle = stringResource(Res.string.feature_activate_dialog_title_success) - dialogTitle = "Error" + dialogTitle = stringResource(Res.string.feature_activate_dialog_title_error) Grep invariant now passes: zero user-facing literals in feature/activate/src/commonMain/kotlin/.
|



Summary
Phase C Wave 1 — first end-to-end feature migration. Establishes the migration template that Waves 2-7 replicate by touching all five layers of the architecture per the revised plan:
core/network→core/database→core/data→core/domain(delete pure delegators) →feature/*({ui, di, navigation}/restructure).Activate is the smallest mutation feature (3 isolated submit endpoints: client / center / group). Picking it first proves the pattern without offline-draft complexity.
Architectural deliverables (seeded for all future waves)
core/data+core/uire-export layerscore/data/build.gradle.kts:api(projects.coreBase.store)→api(projects.core.store)(route through the seam)core/data/.../store/SubmitTypes.kt: typealiasSubmitHandler<R>/SubmitState<R>+CoroutineScope.submitHandler<R>()extensioncore/ui/build.gradle.kts:+ api(projects.coreBase.ui)core/ui/.../store/ViewModelTypes.kt: typealiasBaseViewModel<S, E, A>Feature modules import
com.mifos.core.data.store.*andcom.mifos.core.ui.store.*and never touchtemplate.core.base.store.*directly in their gradle. Composables (SubmitProgressOverlay,SubmitResultHandler,LocalScreenStateDefaults) can't be typealias-ed in Kotlin — they're imported by name via the api chain (feature → core/ui → core-base/ui).core/networkper-resource API rewriteNEW per-resource subpackages following
kmp-project-template/core/network/<feature>/{api,dto}/:core/network/client/api/ClientApi.kt— suspendactivate()returnsPostClientsClientIdResponsecore/network/center/api/CenterApi.kt— suspendactivate()returnsPostCentersCenterIdResponsecore/network/group/api/GroupApi.kt— suspendactivate()returnsHttpResponse(Fineract returns no typed body)Each interface is per-resource (not per-action); future client/center/group feature waves add more methods to the same files.
core/network/di/NetworkModule.kt: 3 newsingle { get<Ktorfit>().createXxxApi() }factories.Method-level
@Deprecatedannotations on legacyDataManagerClient.activateClient,DataManagerCenter.activateCenter,DataManagerGroups.activateGroup,ClientService.activateClient,CenterService.activateCenter,GroupService.activateGroup— those classes have many other live methods, so file-level deprecation isn't appropriate.core/data/activate/(relocate + rewrite)git mvrelocates:core/data/repository/ActivateRepository.kt→core/data/activate/ActivateRepository.ktcore/data/repositoryImp/ActivateRepositoryImp.kt→core/data/activate/impl/ActivateRepositoryImpl.ktActivateRepositoryinterface simplified: drops nullableActivatePayload?;activateGroup()no longer returnsHttpResponse— repository throws on non-2xx.ActivateRepositoryImplnow injects(ClientApi, CenterApi, GroupApi)— noDataManagerdependency.core/data/.../di/RepositoryModule.kt: import update +singleOf(::ActivateRepositoryImpl) bind ActivateRepository::class. Repository binding lives incore/dataper the Koin DI ownership boundary inC-features.md.core/domain— delete 3 pure-delegator use casesActivateClientUseCase.kt(deleted)ActivateCenterUseCase.kt(deleted)ActivateGroupUseCase.kt(deleted)UseCaseModule.kt: 3factoryOf(::ActivateXxxUseCase)entries + their imports removedfeature/activate/{ui, di, navigation}/restructureWas flat (
ActivateViewModel.kt,ActivateScreen.kt,ActivateUiState.kt,ActivateNavigation.ktall at root). Now:VM no longer collects
Flow<DataState>.SubmitHandler<Unit>drives theSubmitting → Submitted | Failedlifecycle. Screen-side terminal effects viaSubmitResultHandler; Submitting overlay viaSubmitProgressOverlay. Submit buttonenabled = !isSubmitting(framework also dedupes double-tap).@Previewrestored usingorg.jetbrains.compose.ui.tooling.preview.*(Compose Multiplatform-aware, NOT the Android-only variant).feature/activate/build.gradle.kts:-implementation(projects.core.domain)++implementation(projects.core.data)++implementation(projects.core.ui). No directcoreBase.*deps.Grep invariants (Wave 1 verification)
grep -rn "DataState|asDataStateFlow" feature/activate/grep -rn "template\.core\.base" feature/activate/KptTheme,SubmitProgressOverlay,SubmitResultHandler— Composables/Theme accessed via api chain; gradle direct deps still zero)grep -rn "implementation\(projects.coreBase" feature/activate/build.gradle.ktsgrep -rn "MifosProgressIndicator|MifosSweetError" feature/activate/{ui, di, navigation}/infeature/activate/src/commonMain/kotlin/.../activate/Test plan
./gradlew :feature:activate:buildcompiles cleanly./gradlew :cmp-android:assembleDebugsucceeds; Koin DI graph resolves; app bootsActivateScreenPreviewrenders in IDE preview paneMigration template for Waves 2-7
Every subsequent wave follows this same shape. The infrastructure pieces (
core/data/store/SubmitTypes,core/ui/store/ViewModelTypes,core/dataapi routing) are seeded by Wave 1 and Waves 2-7 don't redo them. Per-feature additions:core/network/<resource>/api/XxxApi.kt(new resource if first wave touching it, or new methods on existing API)core/data/<feature>/{XxxRepository.kt, impl/XxxRepositoryImpl.kt}+RepositoryModuleregistrationcore/database/<feature>/{dao, entity, mapper}/(when caching is involved)core/domainfeature/<name>/{ui, di, navigation}/restructure withBaseViewModel<S, E, A>+ framework Composables +@Previewfeature/<name>/build.gradle.ktsdeps:core/data + core/uionlyRoomSubmitOutbox<P>+OfflineSubmitSyncer<P, R>in feature DI for offline-resilient flows (savings, recurringDeposit, loan, client)Phase status