RIPDPI's own Compose conventions: ViewModel pattern, Route/Screen split, DataStore->StateFlow, RipDpiThemeTokens. Use for how this app does Compose, not generic Compose API questions (see compose).
日本語の概要は準備中です。原文の説明を表示しています。
RIPDPI Compose performance: stability annotations, compiler metrics, LazyColumn key/contentType, per-screen recomposition notes. Use when diagnosing recomposition, stability, or list performance here.
インストール方法を見るインストールする前に、エージェントに与えられる指示の中身を確認できます。
The Compose compiler decides at compile time whether each class is stable (safe to skip recomposition when equal) or unstable (must always recompose). Classes from non-Compose modules (protobuf, java.time, Room entities) are unstable by default unless declared in the stability config.
@Immutable -- all public properties never change after construction. Use for sealed-class hierarchies and pure value types (DiagnosticsUiModels, HistoryUiModels, theme tokens like Color, Spacing, Shape, Surface, Motion).
@Stable -- property reads return equal values between recompositions, or the runtime is notified on change. Use for state holders with callbacks or MutableState (SettingsUiModels: DesyncCoreUiState, ProxyNetworkUiState, etc.).
activities/DiagnosticsUiModels.kt -- 60+ annotations, @Immutable for data,
@Stable for models with callbacks.activities/SettingsUiModels.kt -- @Stable on all section state holders.activities/HistoryUiModels.kt -- @Immutable on all models (pure data).activities/MainViewModel.kt -- @Immutable on sealed states.ui/theme/ -- @Immutable on all token classes.Rule: every new class passed to a @Composable MUST be annotated. Explicit annotation prevents regressions when a field type changes.
app/compose-stability.conf marks external types as stable:
com.poyka.ripdpi.proto.*, com.google.protobuf.GeneratedMessageLitejava.time.{Instant,Duration,LocalDate,LocalDateTime,ZonedDateTime,ZoneId}kotlin.time.Duration, kotlinx.collections.immutable.*com.poyka.ripdpi.data.{TcpChainStepModel,UdpChainStepModel,ActivationFilterModel,NumericRangeModel}Add new non-Compose-module types here rather than wrapping in UI models.
Convention plugin ripdpi.android.compose.gradle.kts enables reports when
CI=true or -Pripdpi.composeReports=true:
./gradlew :app:assembleGithubFullRelease -Pripdpi.composeReports=true
Output: app/build/compose-reports/ and app/build/compose-metrics/.
| File | Purpose |
|---|---|
*-composables.txt | Restartable/skippable status and param stability |
*-classes.txt | Stability of every class seen by the compiler |
*-composables.csv | Machine-readable, diff between commits |
*-module.json | Module summary: skippable %, restartable count |
Look for "not skippable" composables (unstable parameter) and "unstable"
classes (often a List<T> that should be ImmutableList<T>).
TrackRecomposition(tag) -- SideEffect counting recompositions. Already in
HomeScreen. Add to any suspect composable.RecompositionReportEffect(intervalMs) -- periodic logcat dump. Place once
in root composable. Markers: ! (delta>5), !!! (delta>20).adb logcat -s RecomposeTracker:D RecomposeReport:I *:SEnable "Show Recomposition Counts" in the toolbar. High recomposition count with low skip count = hot path that needs investigation.
The project includes androidx-compose-runtime-tracing. Capture a Perfetto
trace and open in ui.perfetto.dev, filter for Compose: slices.
Every items() call MUST provide key. Without keys, Compose recreates item
state on every list change. Also provide contentType when mixing different
item structures -- the slot pool is keyed by content type.
Project status:
HistorySections.kt): key = { it.id }, contentType = { "connection_session" } / { "diagnostics_session" } / { "event" }key = { it.label }, contentType = { "trend" }key = { _, entry -> entry.id }, contentType = { _, _ -> "log_entry" }Avoid new lambda instances inside items {}. Hoist callbacks:
// Bad: new lambda per recompose
items(list, key = { it.id }) { item ->
Row(onClick = { viewModel.onClick(item.id) })
}
// Good: stable outer lambda
val onClick = remember(viewModel) { { id: String -> viewModel.onClick(id) } }
items(list, key = { it.id }) { item ->
Row(onClick = { onClick(item.id) })
}
DiagnosticsScanSection's live-probe preview (livePreviewProbes) is a
remember-memoized reversed().take(N) window over the growing
completedProbes list, so the raw display index is not stable across new
completions. The list key uses liveProbeItemKey(completedProbeCount, previewIndex): Int = completedProbeCount - previewIndex - 1, which resolves
to each probe's own append-order position in the untruncated list rather
than its position in the preview window -- that position never changes once
assigned, and it no longer risks colliding on repeated target/outcome
pairs the way a literal "$index-${probe.target}-${probe.outcome}" key
would. Follow this pattern (a stable position derived from a monotonically
growing count, not the display index) for any other reversed/truncated
live-preview list.
rememberInfiniteTransition for connecting-state pulse -- correctly scoped
to ConnectionState.Connecting, stops when idle.animateColorAsState, animateFloatAsState for press feedback.AnimatedContent for state transitions.TrackRecomposition("HomeScreen").derivedStateOf for event filtering -- correct pattern.beyondBoundsPageCount = 0 -- do not increase it.derivedStateOf for FAB visibility based on scroll position.key but no contentType (fix opportunity).ImmutableList<LogEntry> before UI exposure.entries.size in header causing parent recomposition on each new
log line. Int is stable, but keep parent scope narrow.item(key = "..."), all state holders are @Stable.items() calls with key +
contentType live in HistorySections.kt, not HistoryScreen.kt.derivedStateOf for empty-state detection.-composables.csv against baselinekey and contentTyperemember lambdas passed to LazyColumn itemsImmutableList for list params to @Composable, not List<T>まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
RIPDPI's own Compose conventions: ViewModel pattern, Route/Screen split, DataStore->StateFlow, RipDpiThemeTokens. Use for how this app does Compose, not generic Compose API questions (see compose).
日本語の概要は準備中です。原文の説明を表示しています。
ADB-based RIPDPI device/emulator debugging: build/install/launch, logcat filtering, fixture port-forwarding, instrumented tests, crash/ANR triage. Use when debugging on a real device or emulator.
日本語の概要は準備中です。原文の説明を表示しています。
RIPDPI Appium launch-contract reference: start routes and permission/service/data presets. Use when a test launches to the wrong screen or a new automation route is added. General flakiness: appium-test-debug.
日本語の概要は準備中です。原文の説明を表示しています。
RIPDPI Appium test authoring: page objects, resource-id locators, assertions, wait tiers. Use when writing a new Appium test or page object, or adding coverage for a new screen.
日本語の概要は準備中です。原文の説明を表示しています。
RIPDPI Appium failure triage: flaky tests, locator/session/wait issues, screenshot and element-tree debugging. Use when an Appium test fails or is flaky.
日本語の概要は準備中です。原文の説明を表示しています。
Add or modify a GitHub Actions job. Use when editing a file under .github/workflows/. Not for running a workflow locally (local-ci-act) or release signing (release-signing).
日本語の概要は準備中です。原文の説明を表示しています。