-
Notifications
You must be signed in to change notification settings - Fork 0
UI Layer Agent
Implements the UI/presentation layer for KMP features. Invoked by creating-kmp-feature skill in Phase 3.
Model: sonnet · Color: purple · Allowed tools: Read, Write, Edit, Glob, Grep, ./gradlew
-
{Feature}UiState.kt— sealed interface, 4 states -
{Feature}UiModel.kt— presentation model -
{Feature}ViewModel.kt— usessetState { copy() } -
presentation/ui/{Feature}Screen.kt— Screen + ScreenRoot + state routing -
presentation/ui/components/— self-contained UI units -
presentation/navigation/—@SerializableRoute +NavGraphBuilder.{featurename}(…)extension
- Follow the UI implementation workflow from
_shared/patterns.md. - Load architecture and design-system references on demand.
- Implement UiState (4-state sealed interface).
- Implement UiModel(s).
- Implement ViewModel with
setState { copy() }. - Implement Screen + ScreenRoot (both required).
- Handle all 4 UI states (Uninitialized / Loading / Success / Failed).
- Implement Navigation with callbacks (no
navControllerin screens). - Validate:
./gradlew :feature:{featurename}:assembleAndroidMain.
// Screen — ViewModel wrapper (NOT tested)
@Composable
fun FeatureScreen(viewModel: FeatureViewModel, onBackClick: () -> Unit) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
FeatureScreenRoot(
uiState = uiState,
onBackClick = onBackClick,
onRetry = viewModel::retry,
)
}
// ScreenRoot — ViewModel-independent (TESTABLE)
@Composable
fun FeatureScreenRoot(
uiState: FeatureUiModel,
onBackClick: () -> Unit,
onRetry: () -> Unit,
) {
// All UI here, X-components only
}Routes contain only data. The NavGraphBuilder extension wires the callbacks:
@Serializable
data object DashboardRoute
fun NavGraphBuilder.dashboard(
onActionClick: (String) -> Unit,
onBackToDashboard: () -> Unit,
) {
composable<DashboardRoute> {
DashboardScreen(
viewModel = koinViewModel(),
onActionClick = onActionClick,
onBackToDashboard = onBackToDashboard,
)
}
}BaseAppNavHost.kt then simply calls dashboard(...) — never composable<Route> { … } directly. This is what makes integration point #4 a one-liner.
{Feature}Screen.kt is the orchestrator and stays lean.
Keep in {Feature}Screen.kt
|
Move to components/{Name}.kt
|
|---|---|
Screen and ScreenRoot
|
Composable that's a "thing" on its own |
State routing (when (uiState)) |
Owns its own structure / identity |
| Top-level layout scaffold | Has private sub-composables |
LoadingContent, ErrorContent
|
Guiding question: "Does this composable have meaning on its own, or only as part of the screen?" — meaning on its own → components/.
Uses X-components from :core:designsystem. The component-mapping reference (using-design-system/references/component-mappings.md) is loaded on demand. No Material3 components in feature files.
When invoked from a design-aware orchestrator the agent reads the blueprint's Component Tree from .claude/docs/{featurename}/designs/{featurename}_blueprint.md and:
- Applies the X-Component Constraint Check for every component the blueprint uses.
- Uses
MaterialTheme.colorScheme.{role}exclusively — never rawColor()hex. - Walks the blueprint's Post-Implementation Checklist before declaring done.
See Design-Pipeline.
## UI Layer Complete: {featurename}
### Files Created
- presentation/{Feature}UiState.kt
- presentation/{Feature}UiModel.kt
- presentation/{Feature}ViewModel.kt
- presentation/ui/{Feature}Screen.kt
- presentation/ui/components/*.kt
- presentation/navigation/{Feature}Navigation.kt
### ScreenRoot Pattern
✅ {Feature}Screen — ViewModel wrapper
✅ {Feature}ScreenRoot — ViewModel-independent
### Rules Followed
✅ setState {} used
✅ All 4 UI states handled
✅ X-components only
✅ ImmutableList for collections
✅ Callback parameters
✅ Build successful
Back to Feature-Development-Agents | Agents