Эх сурвалжийг харах

feat: 神秘黑客技能(很黑客)

HapeLee 1 сар өмнө
parent
commit
97a135cf39

+ 84 - 61
.agents/skills/legado-compose-migration/SKILL.md

@@ -1,78 +1,101 @@
 ---
 name: legado-compose-migration
-description: Guide Legado Android UI migration from XML/View/RecyclerView/DialogFragment screens to Jetpack Compose and Material 3, and guide new Compose-first screens using standard Android architecture. Use when creating, migrating, rewriting, reviewing, or planning a Legado screen, Activity, Fragment, dialog, adapter, navigation destination, or settings page, especially when MainActivity navigation, MVI/UDF, StateFlow/SharedFlow, Koin ViewModels, domain/usecase boundaries, edge-to-edge insets, predictive back, or existing Compose component conventions matter.
+description: Create or migrate a Legado Android screen with Jetpack Compose while preserving behavior and following the repository's Feature ownership, UDF, navigation, DI, inset, and compatibility boundaries. Use for XML/View/RecyclerView/DialogFragment migrations and new Android Compose destinations. Use legado-compose-review instead for review-only requests, and legado-kmp-migration when the change crosses a multiplatform or Gradle boundary.
 ---
 
 # Legado Compose Migration
 
-## Overview
+## Purpose
 
-Migrate one UI surface at a time, or create one new Compose destination at a time. Preserve behavior first for migrations; for newly created screens, prefer standard modern Android architecture over mixed legacy patterns. Use the project's existing `MainActivity` navigation, `BaseComposeActivity` compatibility hosts, `*Screen`, `*Contract`, `*ViewModel`, `StateFlow`, `SharedFlow`, Koin, theme, and widget patterns.
+Deliver one working Android UI surface without importing View-era ownership into new Compose code.
+For migrations, preserve observable behavior before cleanup. For new screens, use the repository's
+current Feature-first and UDF conventions.
 
-Before editing, inspect the target View implementation if one exists, `MainActivity` navigation when adding a destination, and at least two nearby migrated Compose screens. For concrete project patterns, including current Compose state/performance rules, read `references/project-patterns.md`.
+Before editing, read `AGENTS.md`, the relevant part of `docs/dev/feature-first-structure.md`, the
+current implementation and one or two nearby examples that exercise the same behavior. Read
+[references/project-patterns.md](references/project-patterns.md) for repository-specific placement,
+state and host details; examples are evidence, not templates to copy blindly.
 
-## Workflow
-
-1. State assumptions and success criteria.
-   - Name the exact screen or destination being created/migrated.
-   - For migrations, define behavior that must remain unchanged: inputs, result codes, navigation, menu actions, dialogs/sheets, list selection, refresh, persistence, and event bus behavior.
-   - For new screens, define the route owner, UI state, events/effects, domain/usecase boundary, and verification target.
-   - If the target mixes UI and business logic heavily, keep the migration surgical and defer deeper domain cleanup unless needed.
+## Decide the shape
 
-2. Map the old surface.
-   - For migrations, read the Activity/Fragment, XML layouts, adapters, menu XML, dialogs, result launchers, and ViewModel.
-   - For new screens, read `MainActivity`, nearby route screens, the relevant ViewModel/usecase/repository patterns, and shared UI components.
-   - List UI state, user intents, one-shot effects, and external side effects.
-   - Identify reusable Compose components under `ui/widget/components` before creating new components.
-   - Read `docs/dev/feature-first-structure.md`, identify the canonical Feature owner, and avoid
-     adding a second presentation package under legacy `ui/...`.
+- **New destination:** prefer a `MainActivity` Navigation 3 destination. Do not create a standalone
+  Activity without an external-entry or platform lifecycle reason.
+- **Migrated destination:** retain an Activity only as a thin compatibility host when existing
+  callers require stable Android Intent extras/results or a framework contract.
+- **Partial migration:** keep mature View rendering or platform integrations as explicit islands
+  when replacing them would add behavioral risk unrelated to the request.
+- **Multiplatform boundary:** stop treating this as only an Android screen migration and also use
+  `legado-kmp-migration`.
 
-3. Choose the minimal migration shape.
-   - For new Compose-first screens, add a `MainActivity` navigation destination instead of creating a standalone Activity.
-   - Use `BaseComposeActivity` for full-screen Activity migrations only when a legacy Activity must remain as an entry point.
-   - Keep existing Activity Result APIs, `Intent` extras, permission flows, and Android framework calls in the host/compatibility Activity.
-   - If an unreworked View screen still starts the migrated screen with `Intent`, keep the old Activity only as a compatibility host that parses legacy extras and delegates to the Compose screen or `MainActivity` route boundary.
-   - Put renderable state in `UiState`, user actions in `Intent`, and one-shot navigation/framework work in `Effect`.
-   - For new screens, use standard Android/Compose architecture: UDF/MVI-style state hoisting, lifecycle-aware Flow collection, ViewModel-owned state, repository/usecase boundaries, and UI free of business logic.
-   - Ensure the new screen handles edge-to-edge correctly: use `Scaffold` (which respects `WindowInsets` automatically) or apply `Modifier.windowInsetsPadding(WindowInsets.safeDrawing)` on the outermost container. Do not carry over `fitsSystemWindows` / manual padding patterns from XML.
-   - Use mixed legacy patterns only as an integration boundary for unreworked View screens or existing framework contracts.
-   - Use existing repositories/usecases when they already fit; introduce new domain/usecase classes when a new screen needs clean business boundaries or when a migration would otherwise duplicate or entangle business logic.
-
-4. Implement by layers.
-    - Place new Compose-first artifacts under `io.legado.app.feature.<name>` using the repository
-      feature-first convention. Keep an old `ui/...` Activity only as an explicit compatibility
-      bridge when required.
-   - Create or update `FeatureContract.kt` first for `UiState`, `Intent`, `Effect`, dialog/sheet models, and menu action enums.
-   - Update `FeatureViewModel.kt` to expose `uiState: StateFlow<UiState>` and `effects: SharedFlow<Effect>`, with a single `onIntent(...)` entry point unless the existing feature has a simpler established pattern.
-   - Create `FeatureScreen.kt` as a stateless route-level composable: `state`, callbacks, and `onIntent`.
-   - Wire new Compose destinations through `MainActivity` route handling; update a retained Activity only when legacy `Intent` compatibility is required.
-   - Collect state with `collectAsStateWithLifecycle()` and collect effects in `LaunchedEffect(Unit)` from the route or compatibility host.
-   - Register new ViewModels in `di/appModule.kt` with `viewModelOf(::FeatureViewModel)` unless parameters require `viewModel { ... }`.
+## Workflow
 
-5. Remove only obsolete migration artifacts.
-   - Delete XML layouts, adapters, menu resources, binding fields, and imports only when the migrated screen no longer references them.
-   - Do not refactor unrelated View screens or shared utilities.
+1. Bound the surface and success criteria. Record inputs, navigation/results, empty/loading/error
+   states, actions, dialogs/sheets, persistence, back behavior and external effects that must
+   remain.
+2. Read the smallest complete slice: UI, state owner, host/route, DI, resources, data/use-case
+   calls,
+   and any old implementation needed as a behavior reference.
+3. Choose the lowest state owner that reads and writes each value. Put durable business/render state
+   in `UiState`; keep purely local focus, expansion, animation and draft state in the UI when no
+   higher owner needs it. Route Feature/business actions through the Feature intent type. A direct
+   user action that only requests host navigation may use a host callback. Dialog/sheet visibility
+   is UI state; Android framework dialogs, permissions, file pickers and launches stay in the host.
+4. Keep Screen/Content free of repository, DAO, network, storage and service access. New
+   presentation
+   code uses Repository/Gateway/UseCase boundaries. Preserve an existing violation only when the
+   task is explicitly UI-only, do not spread it, and record the remaining debt.
+5. Wire navigation, DI and compatibility behavior at the host boundary. Keep public extras, result
+   codes and deep-link semantics stable unless the user requested an API change.
+6. Delete XML, adapters, bindings and resources only after references and compatibility callers are
+   gone.
+7. Verify in proportion to risk and report both passed evidence and unverified manual/device paths.
 
-6. Verify.
-   - Prefer the smallest Gradle check that compiles the touched app code, usually `.\gradlew.bat :app:compileAppDebugKotlin`.
-   - If resources, manifests, or XML deletion are involved, run `.\gradlew.bat :app:assembleAppDebug` when feasible.
-   - For behavior-heavy changes, add or update focused tests only where the project already has a practical test seam.
+## Project constraints
 
-## Boundaries
+- New ownership lives under `io.legado.app.feature.<name>`; legacy `ui/...` is a migration source,
+  not the destination for a second implementation.
+- Use ViewModel-owned read-only `StateFlow` and one `onIntent` dispatcher for Feature/business
+  actions, as required by `AGENTS.md`. When a Feature has best-effort transient effects, expose the
+  project's `SharedFlow(extraBufferCapacity = 16)` shape. Buffer capacity does not make emissions
+  durable while no UI is collecting: navigation/result/destructive completion and other
+  consistency-critical ViewModel outcomes must reduce to state or use an explicit acknowledgement
+  protocol instead of relying on an effect stream.
+- Annotate Compose-facing `UiState` and UI item models with `@Stable` only while honoring the
+  annotation's contract: public mutable properties and unobservable mutation make it incorrect.
+  Use immutable collections at the rendering boundary; do not change repository/data collections
+  mechanically.
+- On Android, collect `Flow` UI state with `collectAsStateWithLifecycle` unless a documented
+  lifecycle requirement calls for something else. A `LaunchedEffect` collector is scoped to the
+  composition, not automatically to `STARTED`/`RESUMED`. Long-lived effects must use appropriate
+  keys and `rememberUpdatedState` for changing values that should not restart them.
+- Reuse project theme and components when their behavior fits. A wrapper's existence is not enough
+  reason to use it when semantics, accessibility or host requirements differ.
+- Insets are an ownership decision. Inspect `Scaffold` `contentWindowInsets`, whether content
+  consumes `innerPadding`, nested scaffolds/sheets and IME behavior. Do not assume that using
+  `Scaffold` alone proves edge-to-edge correctness, and do not double-apply system-bar padding.
+- Preserve predictive-back behavior. Use ordinary `BackHandler` only for binary interception; use
+  `PredictiveBackHandler` when custom UI needs gesture progress. If Navigation 3 transitions are
+  customized, define and verify predictive-pop behavior as well as the normal pop transition.
+- The app targets API 37, where orientation, aspect-ratio and resizability restrictions no longer
+  protect layouts on large screens. New or substantially migrated destinations must tolerate
+  compact, medium and expanded resizable windows, rotation and recreation. Base layout decisions on
+  the current app window, not physical-device assumptions; do not add an adaptive library or
+  multi-pane layout unless the screen's behavior benefits from it.
+- Prefer Material/Foundation interactions for their semantics, focus and keyboard behavior. Custom
+  controls and gestures must provide appropriate role/state/action semantics, keyboard/D-pad access
+  and an adequate touch target.
+- Keep localized user-facing text in resources and retain fallback behavior.
 
-- Keep Compose functions side-effect-light. Use `LaunchedEffect` for collecting effects and use callbacks for user actions.
-- Do not pass `Activity`, `View`, binding objects, or mutable domain entities deep into composables unless an existing local pattern requires it.
-- Prefer project theme/components: `LegadoTheme`, `AppScaffold`, `ListScaffold`, `AppAlertDialog`, `AppModalBottomSheet`, `RoundDropdownMenu`, top bar helpers, setting items, cover components, and list utilities.
-- Prefer `StateFlow`/`SharedFlow` over `LiveData` for newly migrated Compose surfaces.
-- Annotate `UiState` data classes and UI-facing model wrappers with `@Stable` when they hold collections or entity data passed to composables. Kotlin 2.x strong skipping is on by default, but explicit `@Stable` provides the strongest compiler guarantee and helps document contract boundaries.
-- When using `FeatureIntent` for MVI user actions, distinguish it from Android `Intent` extras and launch APIs in names, comments, and explanations where both appear.
-- For new Compose-first screens, do not copy View-era shortcuts such as UI logic in Activity/Fragment, direct binding-like mutable UI state, adapter-owned state, or Activity-context business operations.
-- Keep one-off Android actions out of `UiState`: navigation, file opening, clipboard, dialogs implemented as Android DialogFragments, result launchers, permission requests, and callbacks that require host context should be `Effect`s handled by `MainActivity` route handling or a compatibility Activity.
-- Prefer `MainActivity` navigation for new Compose destinations. Treat standalone Activities for migrated screens as legacy entry points only when existing View code still depends on `Intent` navigation.
-- For edge-to-edge: this project targets SDK 37, so edge-to-edge is enforced on Android 15+. Use Material 3 `Scaffold` insets or `WindowInsets.safeDrawing` / `safeContent` padding; ensure migrated screens draw behind system bars via `Modifier.windowInsetsPadding()` or top-level scaffold padding rather than manual hardcoded offsets. Migrated screens that relied on `fitsSystemWindows` in XML must be updated.
-- For predictive back: New Compose destinations should work with the predictive back gesture (enabled by default in Navigation 3). When a screen requires back confirmation (unsaved changes, selection mode), use `BackHandler` to intercept and route through `FeatureIntent.BackPressed`.
-- Keep existing Chinese string resources and localization behavior; add strings to resources when user-facing text is new.
+## Verification
 
-## Reference
+- Kotlin-only presentation/host changes: run `:app:compileAppDebugKotlin` using the repository's
+  Gradle wrapper syntax for the current shell.
+- Resource, manifest, XML/binding deletion or packaging changes: run `:app:assembleAppDebug`.
+- State/business changes: run focused ViewModel/use-case tests or add a characterization seam.
+- Navigation/Intent/results/insets/back: manually or instrumentedly exercise every retained entry
+  path that compilation cannot prove.
+- New or substantially changed layouts: exercise representative compact and expanded resizable
+  windows, rotation/recreation and IME interaction rather than validating only one portrait phone.
+- Always run `git diff --check`.
 
-Read `references/project-patterns.md` when implementing or reviewing a migration. It contains project-specific examples, file placement rules, state/effect conventions, and verification commands.
+Use actual task names from this checkout; do not invent tasks from this skill.

+ 146 - 206
.agents/skills/legado-compose-migration/references/project-patterns.md

@@ -1,206 +1,146 @@
-# Legado Compose Migration Project Patterns
-
-## Existing Shape
-
-- App module: `app`.
-- Main source root: `app/src/main/java/io/legado/app`.
-- Legacy UI package: `ui/...`; it remains a migration zone, not the destination for new
-  Compose-first Feature ownership.
-- Canonical new Feature package: `feature/<name>` as defined by
-  `docs/dev/feature-first-structure.md`.
-- Data package: `data/dao`, `data/entities`, `data/repository`.
-- Domain package: `domain/gateway`, `domain/model`, `domain/repository`, `domain/usecase`.
-- DI: `di/appModule.kt`, using Koin `singleOf`, `viewModelOf`, and parameterized `viewModel { ... }`.
-- Main navigation: `ui/main/MainActivity.kt`, using Navigation3 `NavKey`, `rememberNavBackStack`, `entryProvider`, and `NavDisplay`, plus legacy route extras such as `EXTRA_START_ROUTE`.
-- Base Compose host: `base/BaseComposeActivity.kt`, which wraps `Content()` in `AppTheme`, configures system bars, locale/font, background image, and LiveBus recreation events.
-- Compose dependencies are already enabled in `app/build.gradle.kts`; do not add new UI libraries unless the target screen truly requires one.
-- `kotlinx.collections.immutable` is available for Compose-facing screen state. Prefer `ImmutableList`, `ImmutableSet`, and `ImmutableMap` at `UiState` boundaries when collection state is passed to composables and changes often.
-
-## Files to Inspect Before Migrating
-
-Inspect these as local examples, not as APIs to copy blindly:
-
-- `ui/book/info/BookInfoActivity.kt`
-- `ui/book/info/BookInfoContract.kt`
-- `ui/book/info/BookInfoViewModel.kt`
-- `ui/book/info/BookInfoScreen.kt`
-- `ui/book/search/SearchContract.kt`
-- `ui/book/search/SearchViewModel.kt`
-- `ui/book/search/SearchScreen.kt`
-- `ui/main/MainActivity.kt`
-- `ui/main/MainScreen.kt`
-- `ui/widget/components/AppScaffold.kt`
-- `ui/widget/components/list/ListScaffold.kt`
-- `ui/theme/LegadoTheme.kt`
-- `ui/theme/AppTheme.kt`
-
-## Recommended Feature Layout
-
-For a migrated feature, prefer colocated files:
-
-```text
-feature/<feature>/
-  FeatureActivity.kt       # only for legacy Intent compatibility or full-screen migration host
-  FeatureContract.kt
-  FeatureViewModel.kt
-  FeatureRoute.kt          # only when route/host wiring is substantial
-  FeatureScreen.kt
-  components/              # Feature-private UI only
-  sheet/                   # only if sheets are substantial
-  dialog/                  # only if dialogs are substantial
-  model/                   # presentation-only UI models
-  legacy/                  # temporary compatibility bridge with removal condition
-```
-
-Use the smallest set of files. Do not split files just to match this shape.
-
-The directory is `feature/book-info`, the Gradle path is `:feature:book-info`, and the Kotlin
-package is `io.legado.app.feature.bookinfo`. Start with this shape inside `:app`; promote it to a
-Gradle module only after the boundary has real callers and verification. Do not create `api/impl` by
-default.
-
-## MVI and UDF Rules
-
-Use this shape for behavior-heavy screens:
-
-```kotlin
-@Stable
-data class FeatureUiState(
-    val isLoading: Boolean = false,
-    val items: ImmutableList<Item> = persistentListOf(),
-    val dialog: FeatureDialog? = null,
-    val sheet: FeatureSheet = FeatureSheet.None,
-)
-
-sealed interface FeatureIntent {
-    data object BackPressed : FeatureIntent
-    data object Refresh : FeatureIntent
-    data class ItemClick(val id: Long) : FeatureIntent
-}
-
-sealed interface FeatureEffect {
-    data object Finish : FeatureEffect
-    data class OpenDetail(val id: Long) : FeatureEffect
-}
-```
-
-ViewModel rules:
-
-- Keep `_uiState = MutableStateFlow(FeatureUiState())` private and expose `uiState = _uiState.asStateFlow()`.
-- Keep `_effects = MutableSharedFlow<FeatureEffect>(extraBufferCapacity = 8)` private and expose `effects = _effects.asSharedFlow()`.
-- Prefer `fun onIntent(intent: FeatureIntent)` as the UI entry point.
-- Treat `FeatureIntent` as an MVI/user-action type. When Android `Intent` launch/extras are also involved, name variables and explanations clearly enough that the two concepts are not confused.
-- Use `_uiState.update { it.copy(...) }` for state changes.
-- Emit one-shot work through effects, not booleans in state.
-- Keep cached current entities private in the ViewModel when existing project behavior needs mutation or incremental sync, but publish immutable render state.
-- Annotate `UiState` data classes with `@Stable` to give the Compose compiler the strongest stability guarantee. This is especially important when `UiState` fields include collections or entity types that come from non-Compose modules.
-- For collection-heavy `UiState`, convert DAO/repository `List`/`Set`/`Map` values to `kotlinx.collections.immutable` at the ViewModel/UI-state boundary with `toImmutableList()`, `toImmutableSet()`, or `toImmutableMap()`. Do not force repository, DAO, or domain APIs to use persistent collections unless the domain contract truly benefits.
-- Keep internal flow pipelines free to use normal Kotlin collections for sorting, grouping, and persistence work; the immutable collection rule is primarily for values exposed to Compose.
-- Use existing `BaseViewModel.execute { ... }.onSuccess { ... }.onError { ... }` when the surrounding ViewModel already uses that pattern.
-
-Activity rules:
-
-- Prefer `MainActivity` as the owner of Compose-first navigation. Add a Navigation3 route/key and `entryProvider` entry there for new destinations.
-- Extend `BaseComposeActivity` for migrated Activity screens only when a standalone Activity remains necessary.
-- Keep a legacy Activity for a migrated screen only when unreworked View code still calls it through `Intent`; in that case, make the Activity a thin compatibility host that translates extras/results and delegates to Compose state/effects.
-- Keep Activity Result launchers, file openers, clipboard, permission APIs, Android DialogFragments, and framework navigation in the Activity.
-- In `Content()`, collect state with `collectAsStateWithLifecycle().value`.
-- Collect effects in `LaunchedEffect(Unit) { viewModel.effects.collectLatest { ... } }`.
-- Pass only `state` and callbacks into the screen.
-- Edge-to-edge: Since target SDK ≥ 35, edge-to-edge is enforced. In `BaseComposeActivity`, `enableEdgeToEdge()` is called before `setContent`. Ensure screen-level composables apply `Modifier.windowInsetsPadding(WindowInsets.safeDrawing)` on the outermost non-scaffold container, or rely on Material 3 `Scaffold` (which handles `WindowInsets` automatically). Do not use manual `statusBarsPadding()` / `navigationBarsPadding()` on top of scaffold-level insets — double-padding is a common pitfall.
-
-Navigation rules:
-
-- For new Compose-first destinations, add the route to `MainActivity` instead of adding a new Activity.
-- Follow `MainActivity`'s current route style: serializable `NavKey` routes for in-process navigation and launcher `Intent` helper methods only when external or legacy callers need a stable entry point.
-- Keep legacy extra names stable when replacing an old Activity entry point.
-- When a compatibility Activity is retained, let it translate old `Intent` inputs/results; do not make it the source of truth for navigation or business behavior.
-- Predictive back: Navigation 3 enables predictive back by default on Android 14+. New composable destinations work with it automatically. When a screen needs back confirmation (unsaved changes, selection mode, add-to-shelf prompt), use `BackHandler(enabled = ...) { onIntent(FeatureIntent.BackPressed) }` to intercept and let the ViewModel decide — don't bypass ViewModel back logic.
-
-Screen rules:
-
-- Make `FeatureScreen(state, onIntent, ...)` stateless for business state.
-- Use `remember` and `rememberSaveable` only for local UI affordances such as menu expansion, scroll state, transient animation state, and text field drafts when committing through an intent.
-- Use `rememberSaveable` for user-driven transient targets that should survive recreation, such as a delete-confirmation item ID or URL. Store only the stable ID in saved state and resolve the current entity from `UiState`.
-- Use `BackHandler` to route back actions through `FeatureIntent.BackPressed` when ViewModel decisions matter.
-- Keep expensive derived values in `remember(key)` or ViewModel state when they depend on repository data.
-- Prefer `LazyColumn`, `LazyRow`, stable keys, and existing fast-scroller/list components for adapter migrations. For heterogeneous lazy lists or grids, provide `contentType` in addition to `key`.
-- In long-lived `LaunchedEffect` collectors, wrap changing callbacks, `Context`-dependent operations, or lambdas from parents with `rememberUpdatedState` when the effect should not restart.
-- Hoist state only to the lowest common owner that reads and writes it. Move branch-only ViewModel lookup and Flow collection into the branch or a small child composable instead of collecting at a high-level route.
-- Avoid UI-state feedback loops such as `LaunchedEffect(uiState.items) { viewModel.pruneSelection(...) }`. Prefer deriving consistency inside the ViewModel with `combine(...)`, or reduce the state as part of the flow that produces the data.
-
-## Current Compose Performance Notes
-
-- Kotlin 2.x + Compose compiler plugin (`org.jetbrains.kotlin.plugin.compose`) enables **strong skipping by default**. This means composable functions with unstable parameters CAN still be skipped if their arguments are equal by `equals()`. However, ordinary Kotlin `List`, `Set`, and `Map` are still **unstable types** — passing them as parameters prevents the compiler from inferring stability, so changing a list reference still triggers recomposition of callers even with strong skipping.
-- Use `@Stable` on `UiState` data classes and UI model wrappers to give the Compose compiler the strongest stability guarantee. `@Stable` tells the compiler: "if `equals()` says two instances are the same, the UI hasn't changed." This is critical for data classes holding collections.
-- Use `kotlinx.collections.immutable` (`ImmutableList`, `ImmutableSet`, `ImmutableMap`) for render state that crosses into Compose. These are recognized as stable by the Compose compiler. Do not mechanically replace every temporary collection, Room query result, or internal mutable accumulator.
-- If data entities come from modules where the Compose compiler plugin is not applied, consider a `@Stable` UI model wrapper when the entity is passed deeply through composables and causes measurable recomposition cost.
-- Treat `SnapshotStateList` / `SnapshotStateMap` as UI-owned mutable state, not as a default ViewModel `UiState` transport type.
-
-## Clean Architecture Boundaries
-
-This project is mid-migration, so use Clean Architecture pragmatically. For newly created screens, default to standard modern Android architecture and avoid mixed legacy patterns unless they are needed to interoperate with View screens that have not been rewritten.
-
-- For new screens, keep UI, presentation, domain, and data boundaries explicit: Compose renders state, ViewModel owns state and intent handling, usecases hold reusable business actions, repositories mediate data access.
-- For new screens, do not place business rules in Activity/Fragment or composables.
-- For new screens, do not read DAOs directly from UI/presentation unless the project already has no reasonable repository/usecase boundary and adding one would be disproportionate.
-- For migrations, use existing DAOs/repositories directly if the old ViewModel already does and the migration is UI-only.
-- Prefer existing `domain/usecase` classes for reusable business actions such as cache, delete, group update, startup maintenance, reading progress, and WebDAV flows.
-- Add a usecase when a new screen has meaningful business rules, when logic would otherwise be duplicated across features, or when a UI migration needs to extract meaningful business rules from an Activity/adapter.
-- Keep UI-only formatting and Compose layout decisions out of domain.
-- Keep entity-to-display mapping near the UI or ViewModel unless it is reused business language.
-
-## Compose Components and Theme
-
-Prefer project components before raw Material widgets:
-
-- Scaffold/top bars: `AppScaffold`, `GlassTopAppBar...`, `DynamicTopAppBar`, `TopBarActionButton`, `TopBarNavigationButton`.
-- Lists: `ListScaffold`, `TopFloatingStickyItem`, `VerticalFastScroller`, `SelectionBottomBar`.
-- Dialog/sheet: `AppAlertDialog`, `AppModalBottomSheet`, `OptionSheet`, text list input components.
-- Settings: `ClickableSettingItem`, `SwitchSettingItem`, `SliderSettingItem`, `ListSettingItem`, `SettingCard`.
-- Buttons/chips: `AppIconButton`, `SmallIconButton`, `SmallTextButton`, `ToggleChip`, `AlertButton`.
-- Covers/images: `CoilBookCover`, `BookshelfCover`, `buildCoverImageRequest`; use Coil for Compose image loading.
-- Theme: `LegadoTheme.colorScheme`, `LegadoTheme.typography`, `ThemeResolver`, `ProvideThemeOverride` where existing feature behavior needs theme override.
-
-Avoid introducing a second visual system. If a raw Material 3 component is used, style it from `LegadoTheme`.
-
-## Migration Checklist
-
-Before editing:
-
-- Identify old XML layouts, ViewBinding fields, adapters, decorations, menu XML, dialogs, and result contracts.
-- Capture behavior: empty/loading/error states, refresh, search, sort, selection, long click, swipe, menu actions, back behavior, result codes, events, and persisted preferences.
-- Decide whether the task is UI-only or needs domain extraction.
-- For new screens, explicitly decide the presentation/domain/data boundary before coding; do not default to legacy mixed architecture.
-
-During editing:
-
-- Create/update `Contract`, then `ViewModel`, then `Screen`, then `MainActivity` route or compatibility host.
-- For new Compose destinations, wire navigation through `MainActivity` unless a legacy `Intent` caller must remain supported.
-- When retaining an Activity for compatibility, keep it small: parse legacy extras, collect effects, bridge result codes, and avoid placing feature business logic there.
-- Verify edge-to-edge: ensure the screen draws edge-to-edge without overlapping system bars. Use `Scaffold` or `Modifier.windowInsetsPadding(WindowInsets.safeDrawing)`. Remove any `fitsSystemWindows`-based margin hacks from the old layout.
-- Reuse existing string resources and add new resources for new user-facing text.
-- Register the ViewModel in `di/appModule.kt`.
-- Remove only resources made obsolete by this migration.
-- Keep existing public intent extra names and result codes unless explicitly changing an API.
-
-Verification:
-
-- Run `.\gradlew.bat :app:compileAppDebugKotlin` for Kotlin-only changes.
-- Run `.\gradlew.bat :app:assembleAppDebug` when resources, manifests, XML deletion, or generated bindings are affected.
-- If a screen uses Room queries or migration-sensitive repositories, run the relevant existing Android/unit tests if practical.
-- For manual verification, open the migrated screen and compare core flows against the old behavior list.
-
-## Common Pitfalls
-
-- Do not put navigation or Activity launchers inside composables.
-- Do not store one-time navigation as nullable fields in `UiState` unless the existing local pattern already does; prefer `SharedFlow` effects.
-- Do not keep adapter selection state split between old adapter and new Compose state.
-- Do not delete XML/menu resources until all references and generated binding imports are gone.
-- Do not add speculative domain layers for a single migrated screen.
-- Do not use mixed architecture for new screens unless compatibility with unreworked View UI requires it.
-- Do not make a migrated screen's retained Activity the primary navigation model when `MainActivity` can own the Compose destination.
-- Do not bypass `BaseComposeActivity` unless the screen must remain inside a Fragment or legacy host.
-- Do not replace existing event bus or service interactions as part of a UI migration unless required for correctness.
-- Do not forget edge-to-edge: XML `fitsSystemWindows` does not carry over to Compose. Rely on `Scaffold` or `Modifier.windowInsetsPadding()` instead.
-- Do not forget `@Stable` on `UiState` data classes — missing annotations cause unnecessary recomposition, especially when state contains collections.
+# Legado Android Compose Project Patterns
+
+Read this reference while implementing a screen. Confirm every referenced API against the current
+checkout because the repository is mid-migration.
+
+## Ownership and placement
+
+- Android application module: `:app`.
+- Canonical new Feature package: `io.legado.app.feature.<name>`.
+- `ui/...` is legacy/migration territory. Keep only explicit compatibility owners there after a
+  Feature establishes its canonical package.
+- `MainActivity` owns new in-app Navigation 3 destinations and the root graph.
+- A retained Activity translates stable external/legacy Intent inputs and results; it is not a
+  second presentation or navigation owner.
+- App-level Koin aggregation remains in the host. Use the repository's existing `viewModelOf` or
+  parameterized `viewModel` convention rather than adding a second DI pattern.
+
+A Feature may contain Contract, ViewModel, Route, Screen, components, dialogs, sheets and
+presentation models, but create only the files its behavior needs. Directory shape is not an
+acceptance criterion.
+
+## State and effects
+
+For a behavior-heavy new screen, the repository expects:
+
+- `@Stable` Compose-facing `UiState` and UI item models;
+- immutable collections at the Compose rendering boundary;
+- private `MutableStateFlow`, exposed as read-only `StateFlow`;
+- when best-effort transient effects are actually needed, private
+  `MutableSharedFlow(extraBufferCapacity = 16)`, exposed as read-only `SharedFlow`;
+- one `onIntent` dispatcher for user actions;
+- host actions expressed as effects or callbacks.
+
+`@Stable` is an assertion, not a magic optimization. Every public property must remain stable and
+changes observed by Compose. Wrap unstable data only when the wrapper has correct equality and
+mutation semantics. Use measurement/compiler reports before inventing performance abstractions.
+
+Keep source-of-truth business state in the ViewModel. Local `remember`/`rememberSaveable` is
+suitable
+for UI affordances and restorable drafts/IDs, not a second copy of repository state. Avoid
+UI-to-ViewModel feedback loops; restore consistency in the reducer or flow that produces the state.
+
+Classify host actions by delivery semantics:
+
+- A user click whose only meaning is UI navigation can call a host callback directly; use a
+  lifecycle-aware rapid-click guard such as `dropUnlessResumed` where the available Lifecycle
+  version supports it.
+- A ViewModel outcome that must survive a missing collector becomes state with an acknowledgement
+  or another explicit durable protocol.
+- Snackbar/toast/haptic feedback that is intentionally best-effort may use the Feature effect
+  stream. Document the loss behavior rather than assuming `extraBufferCapacity` solves it.
+
+## Host boundaries
+
+Keep these at the route/host unless the project already has a tested abstraction:
+
+- navigation and result delivery;
+- permission and Activity Result launchers;
+- file/document pickers;
+- Android framework dialogs and services;
+- Context-dependent clipboard, URI and external-app operations.
+
+Reusable Screen functions receive state and semantic callbacks. They do not know an Activity,
+binding, DAO, application singleton or root back stack.
+
+## Lists, lifecycle and recomposition
+
+- Use stable keys when item identity survives insertion, removal or reorder; use `contentType` when
+  heterogeneous reuse matters.
+- Collect Android route state with `collectAsStateWithLifecycle` unless another lifecycle is
+  deliberate and documented. Apply the same delivery analysis to effect collectors; composition
+  lifetime and host `RESUMED` state are not interchangeable.
+- Key `LaunchedEffect` by the lifetime it represents. Use `rememberUpdatedState` for changing
+  callbacks captured by an effect that should not restart.
+- Derive cheap display values in composition; memoize or move work only when its cost/lifetime
+  warrants it.
+- Prefer immutable Compose-facing collections, while leaving temporary computations and data-layer
+  APIs in their natural collection types.
+- Strong Skipping is enabled by default on modern Kotlin/Compose compiler versions. Unstable
+  parameters are compared by identity, so avoid needless instance churn, but do not add wrappers or
+  `@Stable` solely from intuition. The repository requires the annotation on UI state/item types;
+  those types must actually satisfy its equality and observable-mutation contract.
+
+## Insets and back
+
+Trace which layer owns each inset. For a Material 3 `Scaffold`, inspect its configured
+`contentWindowInsets` and verify that content applies/consumes the provided padding correctly.
+Sheets, dialogs, IME and nested scaffolds may need separate treatment. Visual/manual evidence is
+required; the presence of `Scaffold`, `safeDrawing` or padding modifiers alone proves nothing.
+
+Navigation 3 integration does not remove the need to verify custom back interception, selection
+mode, unsaved changes and retained Activity entry points. Route back actions through the same owner
+that decides whether leaving is allowed. Use `PredictiveBackHandler` when gesture progress drives
+UI;
+when customizing `NavDisplay` transitions, also supply/test predictive-pop behavior.
+
+## Adaptive layouts and accessibility
+
+Because this app targets API 37, large-screen orientation, aspect-ratio and resizability
+restrictions
+cannot be used as a compatibility fallback. Treat the current app window as dynamic across rotation,
+fold/unfold, split-screen and desktop windowing.
+
+- Test compact and expanded widths for new or substantially migrated destinations.
+- Use current window metrics/window size classes for layout decisions; do not branch on a physical
+  device category or assume portrait.
+- Preserve important input/draft/selection state across recreation with the appropriate local
+  saveable state or `SavedStateHandle`, based on the state owner and size.
+- Do not stretch a phone layout indefinitely. Adopt list-detail/supporting panes or adaptive
+  navigation only when the Feature benefits; avoid adding a library merely to satisfy a checklist.
+- Prefer standard interactive components/modifiers. Custom pointer input needs semantic actions,
+  focus/keyboard access and a usable touch target.
+
+## Architecture boundary
+
+New UI and ViewModels do not add DAO, `appDb`, network-client or old preference access. Use existing
+Gateway/Repository/UseCase contracts, or add the smallest real boundary with an actual caller.
+
+For a strictly UI-only migration, an existing presentation-layer violation may remain to avoid
+combining architecture and UI rewrites. Freeze rather than duplicate it, document it, and do not
+describe the screen as fully modernized until the boundary is corrected.
+
+## Verification selection
+
+- Kotlin-only: `:app:compileAppDebugKotlin`.
+- Resources/manifest/XML/generated binding/package: `:app:assembleAppDebug`.
+- Changed state transitions: focused unit tests with success/failure/cancellation cases as relevant.
+- Navigation or compatibility: exercise the MainActivity route and every retained Intent/result
+  entry.
+- Insets, IME, accessibility and predictive back: device/emulator evidence where risk warrants it.
+- New/substantially changed destinations: representative compact and expanded window checks plus
+  rotation/recreation; add targeted adaptive UI tests when layout branching is meaningful.
+
+Use `AGENTS.md` for the canonical full verification set and exact wrapper command.
+
+## Current official references
+
+- [State hoisting](https://developer.android.com/develop/ui/compose/state-hoisting)
+- [Lifecycle-aware Compose collection](https://developer.android.com/topic/libraries/architecture/lifecycle)
+- [UI event delivery](https://developer.android.com/topic/architecture/ui-layer/events)
+- [Strong Skipping](https://developer.android.com/develop/ui/compose/performance/stability/strongskipping)
+- [Compose side effects](https://developer.android.com/develop/ui/compose/side-effects)
+- [Material Insets](https://developer.android.com/develop/ui/compose/system/material-insets)
+- [Predictive back](https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture)
+- [Adaptive layouts for resizable apps](https://developer.android.com/develop/adaptive-apps/guides/app-orientation-aspect-ratio-resizability)
+- [Compose accessibility defaults](https://developer.android.com/develop/ui/compose/accessibility/api-defaults)

+ 78 - 45
.agents/skills/legado-compose-review/SKILL.md

@@ -1,61 +1,94 @@
 ---
 name: legado-compose-review
-description: Review existing Legado Jetpack Compose code for architecture, behavior, maintainability, and project convention issues. Use when auditing, reviewing, inspecting, evaluating, or finding problems in Legado Compose screens, routes, ViewModels, contracts, dialogs, sheets, navigation, or early Compose implementations, especially for MVI/UDF, StateFlow/SharedFlow, Clean Architecture, MainActivity navigation, legacy Activity compatibility, edge-to-edge insets, @Stable stability annotations, predictive back, and View-era mixed-pattern drift.
+description: Review existing Legado Android Compose screens, routes, ViewModels, contracts, dialogs, sheets, navigation, and compatibility hosts for concrete behavior, lifecycle, state ownership, architecture, inset, accessibility, and maintainability risks. Use for review or audit requests. Do not rewrite code unless fixes are requested; use legado-compose-migration for implementation and legado-kmp-migration for multiplatform or Gradle-boundary findings.
 ---
 
 # Legado Compose Review
 
-## Overview
+## Purpose
 
-Review existing Compose code before rewriting it. Focus on concrete defects, architectural drift, behavior risks, and missing verification, especially in early Compose screens that may predate the current MVI/UDF, Clean Architecture, and `MainActivity` navigation expectations.
+Find concrete defects and risky drift before proposing rewrites. Judge the code against current
+repository behavior and constraints, not a preferred template.
 
-Read `references/review-checklist.md` for the project-specific checklist and severity guidance.
+Read `AGENTS.md`, the scoped files and
+[references/review-checklist.md](references/review-checklist.md). Consult an old View implementation
+only when it remains a caller or behavior baseline.
 
-## Workflow
+## Review workflow
 
-1. Define the review scope.
-   - Identify the exact screen, route, ViewModel, contract, or package under review.
-   - State whether the user wants review only or review plus fixes. If unclear, review first and do not edit code.
-   - Treat unrelated legacy View code as context, not as part of the review, unless it affects the Compose surface.
+1. Define the exact surface and whether the user asked for review only or fixes. Review-only work
+   does not authorize edits.
+2. Inspect the smallest complete behavior slice: Screen, state owner/contract, route/host, DI,
+   relevant repositories/use cases, resources and compatibility entry points.
+3. Trace state and effects end to end. Look for divergent owners, replayed one-shot work, lifecycle
+   mistakes, lost events, unstable identity and error/cancellation paths before style issues.
+4. Trace navigation/results, Insets/IME, back, accessibility and platform launchers through the real
+   host. Do not infer correctness from the presence of a particular API.
+5. Report findings first with tight line references, concrete impact and the smallest credible fix.
+   Separate compatibility-preserving fixes from optional redesign.
+6. If no finding exists, say so and list the important unverified runtime/device paths.
 
-2. Build context from code.
-   - Read the `*Screen`, `*ViewModel`, `*Contract`, host Activity/route, DI registration, and any repositories/usecases used by the feature.
-   - Read the old View implementation only if it is still a compatibility caller or behavior reference.
-   - Compare against nearby current examples such as `BookInfo*`, `Search*`, `MainActivity`, and shared components under `ui/widget/components`.
+## Severity
 
-3. Review by risk, not style preference.
-   - Prioritize behavior regressions, state duplication, lifecycle bugs, navigation bugs, business logic in UI, direct data access from UI/presentation, recomposition hazards, and missing compatibility handling.
-   - Flag style-only issues only when they conflict with established project conventions or make future migration harder.
-   - Do not require broad refactors for a small screen unless the current code creates real behavior or maintenance risk.
+- **P0/P1:** crash, data/behavior loss, broken navigation/result/entry compatibility, unsafe
+  lifecycle or concurrency, repeated destructive effect, inaccessible/obscured critical content.
+- **P2:** state ownership that can diverge, business/data access in UI, platform leakage, incorrect
+  stability contract, recurring recomposition/performance issue, or architecture drift with a real
+  maintenance cost.
+- **P3:** localized convention, naming, testability or cleanup issue without demonstrated behavior
+  or structural impact.
 
-4. Output findings first.
-   - Use code-review style: list findings before summaries.
-   - Include file and tight line references.
-   - Explain the concrete impact and the smallest credible fix.
-   - If using Codex review directives, emit one `::code-comment{...}` per finding.
-   - If no issues are found, say that clearly and mention remaining test/manual verification gaps.
-
-5. Suggest fixes only after findings.
-   - Group fixes into small, reviewable steps.
-   - For architecture drift, separate compatibility-preserving fixes from larger cleanup.
-   - Recommend `legado-compose-migration` only when the finding implies a migration or rewrite workflow.
-
-## Review Priorities
-
-- **P0/P1**: behavior breakage, lost navigation/result behavior, unsafe lifecycle collection, stale state, data corruption, crashes, compatibility entry points that no longer work, or content obscured by system bars (edge-to-edge breakage).
-- **P2**: architecture drift that will cause duplicated logic, hard-to-test behavior, recomposition bugs, incorrect ownership of state/effects, or missing `@Stable` annotations that cause measurable recomposition.
-- **P3**: convention drift, maintainability issues, weak naming, missing previews/tests, or small cleanup that should not block behavior.
+Do not promote a missing annotation, different file split or wrapper choice to P2 without explaining
+the observable or architectural consequence.
 
 ## Boundaries
 
-- Do not rewrite code during a review unless the user asks for fixes.
-- Do not demand pure architecture where a compatibility boundary is necessary for unreworked View screens.
-- For new Compose-first code, expect standard Android architecture: `MainActivity` route ownership, ViewModel-owned state, UDF/MVI user actions, `StateFlow`/`SharedFlow`, repositories/usecases, and UI without business logic.
-- For migrated code, allow a thin retained Activity only for Android `Intent` compatibility with unreworked View callers.
-- Distinguish MVI `FeatureIntent` user actions from Android `Intent` launch/extras when both appear.
-- For edge-to-edge: expect new and migrated Compose screens to handle system bar insets correctly — `Scaffold` or `Modifier.windowInsetsPadding()`, not hardcoded padding or XML `fitsSystemWindows` hacks. Target SDK 37 enforces edge-to-edge on Android 15+.
-- For stability: expect `@Stable` on `UiState` data classes and UI model wrappers. Flag unannotated state classes that hold collections or entity types from non-Compose modules, as they cause unnecessary recomposition.
-
-## Reference
-
-Read `references/review-checklist.md` for detailed checks. When a review turns into implementation, also read `../legado-compose-migration/references/project-patterns.md` if available.
+- Do not require every screen to have the same number of files or abstractions.
+- Allow thin compatibility Activities and mature View/platform islands when they have a current
+  caller and one clear owner.
+- New Compose presentation must not add DAO, network, storage or service access; report existing
+  debt without turning a review into an unauthorized domain rewrite.
+- Treat `@Stable` as a contract that can itself be wrong, not as proof of stability or performance.
+- Strong Skipping is enabled by default with modern Kotlin/Compose compiler versions, so an
+  unstable parameter is not automatically a performance defect. Check identity churn, actual
+  recomposition evidence and compiler/benchmark data. A missing project-required annotation is P3
+  unless it causes a concrete architectural or runtime issue; an incorrect `@Stable` promise can be
+  P2 because it can suppress required updates.
+- Android `Flow` state should normally be collected with `collectAsStateWithLifecycle`. A
+  composition-scoped collector is not automatically lifecycle-aware merely because it uses
+  `LaunchedEffect`.
+- Review each ViewModel-originated `SharedFlow` effect by delivery semantics. Buffer capacity does
+  not make an event durable while no UI is collecting. Navigation, result delivery, payments,
+  destructive completion and other consistency-critical outcomes should normally reduce to state
+  or use an explicit acknowledgement protocol; best-effort transient feedback may remain an effect.
+- UI-only behavior should stay at the lowest UI owner. A direct user navigation callback handled by
+  the host is valid and often preferable to a ViewModel round trip; guard rapid/replayed navigation
+  with lifecycle-aware mechanisms such as `dropUnlessResumed` when the available Lifecycle version
+  supports it.
+- Insets and predictive back require ownership and runtime-path analysis; `Scaffold`, padding APIs,
+  Navigation 3 or `BackHandler` alone do not prove correctness.
+- Use `PredictiveBackHandler` when custom UI needs gesture progress; use ordinary `BackHandler` only
+  when binary interception is sufficient. When Navigation 3 transitions are customized, inspect
+  `predictivePopTransitionSpec` as well as normal pop behavior.
+- Reusing a project component is preferred only when its behavior, semantics and accessibility fit.
+- Prefer Material/Foundation interaction APIs because they provide semantics, focus and input
+  behavior. Custom gestures/components must preserve role, state/action semantics, keyboard/D-pad
+  access and an adequate touch target.
+
+## Current official baseline
+
+When library behavior matters, verify it against the repository version and current official docs:
+
+- [Strong Skipping](https://developer.android.com/develop/ui/compose/performance/stability/strongskipping)
+  and [stability contracts](https://developer.android.com/develop/ui/compose/lifecycle).
+- [Lifecycle-aware Compose collection](https://developer.android.com/topic/libraries/architecture/lifecycle).
+- [State hoisting](https://developer.android.com/develop/ui/compose/state-hoisting).
+- [UI event delivery](https://developer.android.com/topic/architecture/ui-layer/events).
+- [Material 3 Insets](https://developer.android.com/develop/ui/compose/system/material-insets)
+  and [Compose Insets](https://developer.android.com/develop/ui/compose/system/insets-ui).
+- [Predictive back](https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture)
+  and [Navigation 3 transitions](https://developer.android.com/guide/navigation/navigation-3/animate-destinations).
+- [Compose accessibility defaults](https://developer.android.com/develop/ui/compose/accessibility/api-defaults)
+  and [semantics](https://developer.android.com/develop/ui/compose/accessibility/semantics).
+
+When implementation is requested, also read the migration skill's project patterns.

+ 85 - 119
.agents/skills/legado-compose-review/references/review-checklist.md

@@ -1,121 +1,87 @@
 # Legado Compose Review Checklist
 
-## Files to Read
-
-For a feature review, inspect the smallest complete slice:
-
-- `FeatureScreen.kt` and any `FeatureSheets.kt` / `FeatureDialogs.kt`.
-- `FeatureViewModel.kt`.
-- `FeatureContract.kt` if present.
-- Canonical owner under `feature/<name>` or the documented reason it remains in legacy `ui/...`.
-- Host route in `MainActivity.kt` or retained `FeatureActivity.kt`.
-- Koin registration in `di/appModule.kt`.
-- Repositories/usecases used by the feature.
-- Old View caller or XML/adapters only when still used for compatibility or behavior comparison.
-
-Use current examples as references:
-
-- `ui/main/MainActivity.kt` for Navigation3 route ownership.
-- `ui/book/info/BookInfoContract.kt`, `BookInfoViewModel.kt`, `BookInfoScreen.kt` for behavior-heavy MVI/UDF.
-- `ui/book/search/SearchContract.kt`, `SearchViewModel.kt`, `SearchScreen.kt` for route-level search state.
-- `ui/widget/components/...` and `ui/theme/...` for shared UI conventions.
-
-## Architecture Checks
-
-Flag issues when:
-
-- A new Compose-first screen is implemented as a standalone Activity instead of a `MainActivity` destination without a compatibility reason.
-- A retained Activity contains feature business logic instead of acting as a thin Android `Intent` compatibility host.
-- Business rules, repository calls, DAO calls, persistence writes, or service orchestration live in composables.
-- ViewModel exposes mutable state directly, exposes `MutableStateFlow`, or lets UI mutate domain objects.
-- UI state is split across Activity fields, composable `remember` state, adapter state, and ViewModel state in a way that can diverge.
-- One-shot events such as navigation, result codes, file opening, permission requests, or clipboard writes are represented as persistent `UiState` fields that can replay incorrectly.
-- MVI `FeatureIntent` user actions are confused with Android `Intent` launch/extras.
-- Domain/usecase boundaries are bypassed in new screens when a meaningful business action exists.
-- A migration introduces new domain abstractions for a single trivial UI action without reducing real complexity.
-- New Compose-first files extend a legacy `ui/...` package after the Feature has a canonical
-  `feature/<name>` owner, or the same Feature is split across both roots without an explicit
-  compatibility boundary.
-
-## UDF and State Checks
-
-Flag issues when:
-
-- `Screen` functions own business state instead of receiving `state` and callbacks.
-- `remember` / `rememberSaveable` stores source-of-truth data that should survive process or route recreation through ViewModel state.
-- `LaunchedEffect` keys are unstable or cause repeated data loading, duplicate navigation, duplicate toasts, or repeated service calls.
-- Flows are collected without lifecycle awareness in UI routes where `collectAsStateWithLifecycle()` should be used.
-- List items lack stable keys where mutation, selection, or animation can make state attach to the wrong row.
-- Selection, search query, sorting, filtering, loading, and error states are not represented in a single coherent `UiState`.
-- Derived values are recomputed expensively on every recomposition instead of living in ViewModel state or `remember(key)`.
-
-## Navigation and Compatibility Checks
-
-Flag issues when:
-
-- New Compose destinations skip `MainActivity` Navigation3 route registration.
-- Legacy Android `Intent` extras or result codes change without an explicit compatibility plan.
-- A migrated screen is reachable both through `MainActivity` and a retained Activity with inconsistent state initialization.
-- Navigation is performed directly inside nested composables instead of through callbacks/effects.
-- Activity Result launchers, file pickers, permission requests, or Android DialogFragments are hidden inside reusable UI composables.
-- Back behavior bypasses ViewModel decisions when unsaved changes, selection mode, add-to-shelf prompts, or confirmation dialogs exist.
-
-## UI and Project Convention Checks
-
-Flag issues when:
-
-- Raw Material components ignore `LegadoTheme` or existing shared components where the project already has a wrapper.
-- A screen recreates shared UI already present under `ui/widget/components`.
-- User-facing text is hardcoded instead of using string resources, except for temporary debug-only text.
-- Compose UI keeps ViewBinding, adapter, or XML assumptions after migration.
-- Dialogs and bottom sheets use inconsistent project components when `AppAlertDialog`, `AppModalBottomSheet`, or existing option sheets fit.
-- Image loading bypasses existing Coil cover/image helpers where cover behavior, cache, SVG, or GIF handling matters.
-
-## Clean Architecture Checks
-
-For new screens, expect:
-
-- Compose renders state and emits user actions.
-- ViewModel owns state, intent handling, and effect emission.
-- Usecases own reusable business actions.
-- Repositories mediate data access.
-- DAOs remain behind repositories unless adding a boundary would be disproportionate and local project patterns already allow direct use.
-
-For migrated screens, allow pragmatic intermediate code only when:
-
-- It preserves behavior.
-- It is isolated behind a clear compatibility boundary.
-- It does not spread View-era assumptions into new Compose-first code.
-
-## Output Template
-
-Use this shape unless the user asks for another format:
-
-```text
-Findings
-- [P1] Title
-  file:line
-  Impact: ...
-  Fix: ...
-
-Open Questions
-- ...
-
-Notes
-- No issues found in ... / Tests not run ...
-```
-
-When using Codex inline review comments, emit one directive per finding with tight line ranges:
-
-```text
-::code-comment{title="[P2] Keep route state in ViewModel" body="..." file="/absolute/path/FeatureScreen.kt" start=42 end=45 priority=2 confidence=0.8}
-```
-
-## Verification Suggestions
-
-Recommend verification based on the reviewed change:
-
-- Kotlin-only review/fix: `.\gradlew.bat :app:compileAppDebugKotlin`.
-- Resource/XML/manifest impact: `.\gradlew.bat :app:assembleAppDebug`.
-- Navigation or compatibility changes: manually open both `MainActivity` route and any retained legacy Android `Intent` entry point.
-- Behavior-heavy ViewModel changes: add or run focused tests only where the project has a practical test seam.
+Use only the sections relevant to the scoped surface.
+
+## Context
+
+- Confirm the canonical Feature owner and whether any legacy `ui/...` file is a compatibility owner.
+- Read Screen/Content, ViewModel/state contract, route/Activity, DI and directly used domain/data
+  contracts.
+- Read old XML/View/adapters only when needed for parity or a live caller.
+- Identify all entry points: MainActivity route, Intent/deep link, notification/widget/service or
+  Activity Result.
+
+## Behavior and state
+
+- Is there one durable state owner, or can ViewModel, Activity, `remember`, adapter and repository
+  copies diverge?
+- Is each state hoisted only to the lowest owner that needs to read and write it? Local expansion,
+  focus or animation state need not be forced into a ViewModel; business state must not be copied
+  into local Compose state.
+- Can loading, empty, error, selection, query, sorting and dialog/sheet states be reconstructed
+  after
+  recreation where required?
+- Are one-shot effects consumed once without becoming persistent replayable flags?
+- Can a ViewModel-produced effect be emitted while the route has no active collector? If losing it
+  would leave navigation, results or durable business state inconsistent, model/acknowledge it as
+  state rather than relying on `SharedFlow` buffering.
+- Are success, failure, cancellation and stale-response ordering handled?
+- Do `LaunchedEffect` keys match the intended lifetime and avoid duplicate loads/navigation/toasts?
+- Is `Flow`-backed Android UI state collected with `collectAsStateWithLifecycle`, or is there a
+  documented lifecycle reason for another collector?
+- Are mutable collections/entities exposed in a way that invalidates a claimed `@Stable` contract?
+- With Strong Skipping, are unstable parameters recreated on each update and compared by identity?
+  Ask for compiler/measurement evidence before treating ordinary recomposition as a defect.
+- Do lazy items use stable identity where insert/remove/reorder can attach state to the wrong row?
+
+## Architecture and ownership
+
+- Screen/Content renders state and emits semantic actions; it does not access DAO, repository,
+  network, storage, service or global application state.
+- New ViewModel code uses Gateway/Repository/UseCase boundaries and does not add DAO or old global
+  access.
+- Host owns navigation, framework launchers, permissions, Context/URI work and root lifecycle.
+- A retained Activity translates compatibility inputs/results without becoming a second business or
+  navigation owner.
+- Feature code does not depend on another Feature implementation.
+- New files do not extend a legacy package after a canonical Feature owner exists.
+
+## Navigation and compatibility
+
+- New in-app destinations are registered in the current MainActivity graph unless an external-entry
+  requirement justifies an Activity.
+- Existing extras, result codes, deep links and callers preserve semantics.
+- Multiple entry paths initialize equivalent state and converge on the same owner.
+- Nested UI requests navigation through callbacks/effects rather than mutating the root stack.
+- Pure UI navigation can call a host callback directly. Check rapid taps and lifecycle state; use a
+  lifecycle-aware guard such as `dropUnlessResumed` where supported rather than inventing a
+  ViewModel effect solely to move the navigation call.
+- Back interception covers real selection/unsaved state without breaking predictive back or host
+  result delivery.
+- Custom gesture-progress UI uses `PredictiveBackHandler`; customized Navigation 3 pop transitions
+  also define/verify predictive-pop behavior.
+
+## UI, Insets and accessibility
+
+- Trace `Scaffold` `contentWindowInsets`, inner padding consumption, nested scaffolds/sheets, system
+  bars and IME. Check for both overlap and double padding.
+- User-facing strings retain localization and fallback behavior.
+- Project components/theme are used when their semantics fit; wrappers are not applied mechanically.
+- Touch targets, focus order, semantics/content descriptions and keyboard behavior fit the control.
+- Custom pointer input has an accessible semantic action and keyboard/D-pad alternative; standard
+  Material/Foundation interactions are preferred when their behavior fits.
+- Lists/images preserve existing cache, loading and accessibility behavior where relevant.
+- Removed XML/binding/menu resources have no remaining references.
+
+## Verification and output
+
+Tie each finding to evidence and the smallest fix. Recommend only checks that can observe the risk:
+
+- compilation for Kotlin wiring;
+- assemble for resources, manifest, XML/binding and packaging;
+- focused unit tests for reducers/ViewModels/use cases;
+- route plus retained Intent/result path checks for navigation compatibility;
+- device/emulator checks for Insets, IME, accessibility, predictive back and rendering performance.
+
+Use P0/P1/P2/P3 from the skill. If there are no findings, explicitly list what was not executed or
+observed.

+ 126 - 92
.agents/skills/legado-kmp-migration/SKILL.md

@@ -1,99 +1,133 @@
 ---
 name: legado-kmp-migration
-description: Plan, implement, or review Legado Gradle modularization and Kotlin/Compose Multiplatform migration slices. Use for build-logic, module boundaries, commonMain extraction, expect/actual or platform interfaces, CMP feature sharing, KMP migration gates, capability matrices, and related scaffolding. Do not use for an ordinary Android-only Compose screen migration unless it also changes a multiplatform or Gradle boundary.
+description: Plan, implement, or review Legado KMP-first modularization across shared domain, data, and presentation code with independently chosen Android, Desktop, Windows-native, and optional iOS renderers. Use for Gradle boundaries, commonMain extraction, platform capabilities, native or process bridges, selective CMP, target gates, and legacy-owner removal. Do not use for an Android-only View-to-Compose rewrite unless it also changes a multiplatform or Gradle boundary.
 ---
 
-# Legado KMP/CMP Migration
+# Legado KMP-first Migration
 
 ## Purpose
 
-Move one verified boundary toward KMP/CMP without weakening Android behavior. Treat
-platform-specific implementations as valid architecture, keep the Android app shippable after every
-slice, and use baselines as ratchets rather than waivers.
-
-Before acting, read repository `AGENTS.md` and `docs/dev/kmp-cmp-modernization.md`. For an
-implementation or review, also read [references/slice-checklist.md](references/slice-checklist.md).
-
-## Select the mode
-
-- **Plan:** produce a dependency inventory, target seam, capability impact, phases, gates and
-  rollback point. Do not create the full target module tree.
-- **Extract:** move a narrow set of models/rules/contracts behind adapters, migrate callers, run
-  both common and Android verification, then lower relevant baselines.
-- **Build logic:** add or change convention plugins and dependency rules using one representative
-  module before broad rollout.
-- **CMP feature:** share state/reducer/UI only where platform effects and navigation have explicit
-  host boundaries.
-- **Scaffold:** generate only conventions proven by at least two accepted manual examples; require
-  dry-run and no-overwrite behavior.
-- **Review:** report behavior and boundary findings before recommending changes. Do not edit unless
-  fixes were requested.
-
-## Required workflow
-
-1. Define the slice and evidence.
-    - Name exact source files/packages, current callers, intended target module/source set, and
-      behavior that must not change.
-    - Separate current facts from planned modules and task names.
-    - Inspect Gradle files, version catalog, relevant tests, and imports rather than assuming an API
-      is multiplatform.
-    - For Feature work, read `docs/dev/feature-first-structure.md` and distinguish package
-      colocation, Android module extraction, and KMP conversion as separate stages.
-
-2. Classify every dependency.
-    - `common-ready`: Kotlin/common library API with a non-Android compile target.
-    - `contract-needed`: behavior can be represented by a narrow interface and platform
-      implementation.
-    - `platform-island`: lifecycle, service, reader rendering, Rhino/JVM or another capability that
-      should remain platform-specific.
-    - `unknown`: verify against primary documentation or a compile PoC before designing around it.
-
-3. Choose the smallest seam.
-    - Prefer stable values, pure rules, gateways and use cases before storage, engines and UI.
-    - Prefer ordinary interfaces plus DI over `expect/actual`; reserve `expect/actual` for platform
-      primitives that every target must provide statically.
-    - Keep platform assembly in the app/Koin composition root.
-    - Do not change storage, network, navigation, DI and UI technology in the same slice.
-    - Create Feature `api/impl` only when a real Gradle boundary and caller require it.
-
-4. Establish gates before moving code.
-    - Add characterization/contract tests at the old boundary.
-    - Use report → freeze baseline → blocking for a new rule.
-    - Never raise a platform-import or dependency baseline to make a migration pass.
-
-5. Implement additively.
-    - Introduce contract/module and Android adapter first.
-    - Migrate a bounded caller set.
-    - Delete the old entry only after no callers remain; otherwise document its removal condition.
-    - Keep app-host composition, navigation runtime and platform effects outside shared UI.
-    - Preserve the canonical `io.legado.app.feature.<name>` package when promoting `:app` code to
-      `:feature:<name>` so module extraction does not require another conceptual reorganization.
-
-6. Verify and report.
-    - Always retain the repository G0 Android gates.
-    - For common code, run the actual `commonTest`, metadata, and selected non-Android target
-      compile tasks that exist in the changed project.
-    - For adapters, run contract and Android behavior tests. For reader/rule/service changes,
-      include parity or performance evidence proportional to risk.
-    - Report exact commands, dependency/baseline deltas, capability changes, rollback path, and
-      unverified targets.
-
-## Non-negotiable boundaries
-
-- No Android, JDK-only, Room DAO/entity, resource ID, `File`, URI, service, Activity or View type in
-  shared public contracts.
-- No core → feature, Feature `api` → Feature, or Feature `impl` → another Feature `impl` dependency.
-- No silent no-op implementation for an unsupported target; model the capability explicitly.
-- Report target support by evidence level: compile, contract-test, smoke, package, and
-  release-ready. Do not collapse them into one supported/unsupported flag.
-- No generic dumping-ground module and no empty architecture-shaped modules.
-- Do not make Compose reader replacement a KMP prerequisite; share render models while allowing the
-  Android renderer to remain specialized.
-- Preserve current rule-script, import/export, database, settings and reader semantics until
-  dedicated tests authorize a behavior change.
-
-## Relationship to Android Compose work
-
-Use `legado-compose-migration` for Android-only View→Compose work and `legado-compose-review` for
-Android Compose review. Combine them with this skill only when the same bounded change crosses a
-KMP/CMP or Gradle-module boundary.
+Move one verified responsibility toward a reusable KMP boundary without weakening Android behavior
+or forcing every host to use Compose. Optimize for shared behavior and explicit platform contracts,
+not maximum shared UI percentage.
+
+Valid renderers include Android Compose/View islands, Compose Desktop with Material or Fluent,
+Windows-native WinUI 3, and optional SwiftUI/UIKit. CMP is one renderer technology, not the
+architecture root.
+
+Before acting, read repository `AGENTS.md`, `docs/dev/kmp-cmp-modernization.md`, and the relevant
+part of `docs/dev/feature-first-structure.md`.
+
+Read supporting references only when applicable:
+
+- Implementation or review: [references/slice-checklist.md](references/slice-checklist.md).
+- Multiple renderers, WinUI 3, SwiftUI/UIKit, native export or IPC:
+  [references/renderer-host-strategy.md](references/renderer-host-strategy.md).
+
+## Choose the work mode
+
+- **Architecture/plan:** inventory current dependencies and consumers, then choose the sharing
+  boundary, renderer strategy, targets, gates and rollback point.
+- **Pure KMP extraction:** move stable models, rules, ports, use cases, reducers or stores into
+  Compose-free `commonMain`.
+- **Data/runtime extraction:** separate domain contracts from storage, network, files and rule
+  engines while retaining compatibility semantics under contract tests.
+- **Renderer boundary:** split renderer-neutral presentation from Material/Miuix, Fluent, WinUI 3,
+  SwiftUI/UIKit or specialized reader UI.
+- **Selective CMP:** share a Compose Screen/resource set only when real hosts deliberately choose
+  the same renderer and interaction model.
+- **Native host bridge:** expose a small, versioned API through an Apple framework, Windows DLL/C
+  ABI, or process/IPC boundary; do not export the internal Kotlin object graph.
+- **Build logic:** prove a target/convention/gate change with one representative module before
+  broader rollout.
+- **Review:** report findings and unverified targets first; edit only when fixes or plans were
+  requested.
+
+## Decisions required before implementation
+
+1. **What is shared?**
+    - Default candidates are domain values, business rules, repository ports, use cases,
+      serialization models, state snapshots, commands and reducers.
+    - Screens, design-system components, navigation renderers, platform ViewModel owners, resource
+      handles, lifecycle and OS integration are not shared by default.
+    - A pure presentation API must not expose Compose annotations/types, Material/Fluent types,
+      Android resources, Koin, Room, or platform SDK objects.
+
+2. **Who renders it?**
+    - Name each committed host and renderer separately; “Desktop” is not a renderer.
+    - Different renderers may consume the same presentation contract while owning distinct layouts,
+      resources, navigation and accessibility.
+    - Do not invent universal UI wrappers across unrelated design systems. Share semantic values
+      only
+      when their meaning is genuinely common.
+
+3. **How does a non-Kotlin host consume it?**
+    - JVM Compose hosts can depend on KMP/JVM modules directly.
+    - SwiftUI/UIKit needs a deliberately exported Apple framework facade.
+    - WinUI 3 needs either a Kotlin/Native `mingwX64` DLL with a narrow C ABI or a separately
+      packaged
+      JVM process reached through a versioned local IPC protocol.
+    - Keep rich Kotlin types internal. Define DTO/state snapshots, commands, errors, cancellation,
+      callback threads, allocation ownership and disposal at the bridge.
+
+4. **What evidence supports the claim?**
+    - Distinguish metadata/compile, contract-test, host smoke, package and release-ready evidence.
+    - Android plus JVM compilation does not prove iOS or Windows Native readiness.
+    - Do not document a module, target, task or host as current until it exists in this repository.
+
+## Workflow
+
+1. Bound one slice: exact files, callers, behavior, intended owner/source set, host impact and
+   rollback point.
+2. Classify dependencies as `common-ready`, `contract-needed`, `renderer-specific`,
+   `platform-island`, or `unknown`. Imports are only a first pass; compile every claimed target.
+3. Establish characterization or contract tests before moving behavior. Preserve storage, script
+   ABI, serialization, error, cancellation, ordering and threading semantics.
+4. Choose the smallest seam. Prefer interfaces plus constructor injection; use `expect/actual` only
+   for true platform primitives or a documented case where injection cannot provide the boundary.
+5. Implement additively, migrate a bounded caller set, then delete the old owner when no callers
+   remain. Do not keep internal `Help/Utils/Base/Provider` facades for import compatibility.
+6. Run actual repository gates and target tasks. Never infer task support from the target design.
+7. Report behavior evidence, dependency/baseline delta, capability changes, exact commands,
+   rollback path and unverified hosts/devices.
+
+## Boundary rules
+
+- Pure KMP domain/presentation does not depend on Compose, AndroidX ViewModel, Room entities/DAO,
+  Koin, platform resources, `File`/URI, JVM-only libraries or renderer types.
+- Renderer modules may depend on presentation; presentation never depends on a renderer.
+- Each host owns its composition root, root navigation/window lifecycle, platform effects,
+  implementation selection and packaging.
+- Unsupported capabilities are explicit; a successful no-op is not an implementation.
+- Feature-to-Feature implementation dependencies remain forbidden. Create `api/impl` only for a
+  real Gradle consumer; split renderers by responsibility instead of generic layering names.
+- Do not change storage, network, DI, navigation and UI technology in one slice.
+- Historical baselines only decrease. A new source set starts at zero and cannot justify raising a
+  baseline.
+
+## Verification minimums
+
+- Always retain the affected Android G0 gates.
+- Pure KMP: common tests plus every claimed target compile.
+- Data/runtime: adapter contracts and compatibility checks proportional to storage, serialization,
+  cancellation and error risks.
+- CMP renderer: target compile plus rendered UI/semantics smoke and runtime dependency alignment.
+- WinUI 3 DLL: native link, generated API review, consumer smoke, memory/disposal/error/thread tests
+  and package loading.
+- WinUI 3 IPC: protocol compatibility, startup/shutdown/reconnect, local-access policy,
+  cancellation and installer smoke.
+- iOS native UI: framework export plus a Swift consumer compile/observation/lifecycle smoke.
+- Always run `git diff --check`; use a clean rebuild after cross-module moves.
+
+## Review output
+
+List findings by impact with tight file/line evidence and the smallest credible fix:
+
+- P0/P1: behavior or data loss, ABI/storage incompatibility, broken target, lifecycle/thread/
+  cancellation defect, or package/runtime failure.
+- P2: renderer leakage into presentation, illegal dependency, false capability, untested bridge,
+  state duplication, baseline relaxation, or unnecessary abstraction.
+- P3: convention, naming, documentation, graph or template drift.
+
+If there are no findings, state which host, package, device, renderer, interop and performance paths
+remain unverified.

+ 3 - 3
.agents/skills/legado-kmp-migration/agents/openai.yaml

@@ -1,4 +1,4 @@
 interface:
-  display_name: "Legado KMP Migration"
-  short_description: "Plan and review Legado KMP/CMP migration slices"
-  default_prompt: "Use $legado-kmp-migration to plan a reviewable KMP/CMP migration slice with explicit gates."
+  display_name: "Legado KMP-first Migration"
+  short_description: "Plan shared KMP logic and independent native or Compose renderers"
+  default_prompt: "Use $legado-kmp-migration to plan a reviewable KMP-first slice with explicit host, renderer, interop, and verification boundaries."

+ 71 - 0
.agents/skills/legado-kmp-migration/references/renderer-host-strategy.md

@@ -0,0 +1,71 @@
+# Renderer and Host Strategy
+
+Read this reference when a Feature may use more than one UI stack or when a non-Kotlin host consumes
+shared Kotlin behavior.
+
+## Separate three decisions
+
+1. **Shared behavior:** domain, repositories, use cases, state snapshots, commands and reducers.
+2. **Host integration:** Gradle/JVM, Apple framework, Windows DLL/C ABI, or process/IPC.
+3. **Renderer:** Android Material/Miuix, Compose Material/Fluent, WinUI 3, SwiftUI/UIKit, or a
+   specialized platform island.
+
+Do not infer one decision from another. A KMP core does not require CMP, and Compose on one host
+does
+not require every host to share the same Screen.
+
+## Renderer-neutral presentation
+
+Prefer immutable state snapshots, stable IDs, commands/intents, effects, domain values and explicit
+capability/error states. Keep Composable lambdas, `Modifier`, Material/Fluent types,
+`StringResource`, icons/painters, navigation UI objects, Android Context, Swift/WinRT objects, Room
+entities and DI containers out of the boundary.
+
+`@Stable` belongs to a Compose-facing contract or adapter. If WinUI 3 or SwiftUI also consumes the
+state, keep the pure state free of renderer annotations and adapt it at the Compose boundary.
+
+## WinUI 3 choices
+
+WinUI 3 cannot consume a Kotlin/JVM Gradle module like a Kotlin host. Choose the bridge explicitly.
+
+### Kotlin/Native DLL and C ABI
+
+Use a `mingwX64` shared library when in-process calls and one packaged process matter, and only when
+the required dependency closure supports the target.
+
+- Export a small facade, not repositories, Flow, sealed hierarchies or generic Kotlin APIs.
+- Use opaque handles with explicit create/dispose.
+- Define ownership for every returned string or buffer.
+- Map async work to request IDs plus callbacks/polling; define callback thread and cancellation.
+- Version the ABI and compile a native consumer in CI.
+- Do not force JVM-only Room, Rhino or jsoup dependencies into the DLL; split a native-compatible
+  core or choose IPC for those capabilities.
+
+### JVM sidecar and IPC
+
+Keep the existing JVM data/runtime stack in a separately packaged process and expose a versioned
+local protocol when reuse is more important than in-process integration.
+
+- Define protocol DTOs and version negotiation.
+- Define startup, readiness, shutdown, crash recovery and upgrade behavior.
+- Restrict local access; do not unintentionally expose an unauthenticated network service.
+- Propagate cancellation and structured errors.
+- Package and test both processes as one product.
+
+Use one bounded read/write Feature to compare cold start, call latency, state mapping, database and
+runtime reuse, memory ownership, crash isolation, installer complexity and test ergonomics before
+choosing the product-wide bridge.
+
+## iOS native renderer
+
+Export a deliberately small Apple framework facade. SwiftUI may observe a tested shared state host
+or own an `ObservableObject` that calls shared repositories/use cases. Verify Swift names,
+optionality, collections, async/Flow bridging, lifecycle, cancellation and memory ownership with a
+Swift consumer test.
+
+## Selective CMP
+
+Share a CMP Screen only when real hosts intentionally share its visual and interaction model.
+Resources and design-system components belong to that renderer. If Desktop uses WinUI 3 and iOS
+uses SwiftUI, Android Compose can remain an Android renderer even when its code is technically
+portable.

+ 58 - 54
.agents/skills/legado-kmp-migration/references/slice-checklist.md

@@ -1,78 +1,82 @@
-# KMP/CMP Slice Checklist
+# KMP Slice Checklist
 
-Read this reference for implementation plans, extraction work, scaffolding, or reviews.
+Use this reference for implementation plans, extraction work, scaffolding or reviews. The default
+path is renderer-neutral KMP. CMP checks apply only after a shared Compose renderer has been chosen;
+they are not instructions to convert every Feature or host to Compose.
 
 ## Before change
 
-- Scope names exact packages/files and one target boundary.
-- Existing behavior is captured by tests or a written manual parity list.
-- Current callers and module edges are known.
-- Imports/dependencies are classified as common-ready, contract-needed, platform-island, or unknown.
-- The chosen non-Android target and actual Gradle verification tasks are known.
-- A rollback point exists and does not require reverting unrelated work.
+- Scope names exact files/packages, current callers, one boundary and one rollback point.
+- Existing behavior is captured by tests or a written parity list.
+- The target owner, source set and consumers are real rather than copied from the target diagram.
+- Dependencies are classified as common-ready, contract-needed, renderer-specific,
+  platform-island or unknown.
+- Actual Gradle target and verification task names have been inspected.
 
 ## Contract quality
 
-- Public types express domain values, not Android/JVM/storage implementation details.
-- Error, cancellation, threading, transaction, ordering and serialization semantics are explicit.
-- Capability absence is representable and visible to callers.
+- Public types express domain values instead of Android/JVM/storage/rendering details.
+- Error, cancellation, threading, transaction, ordering, serialization and ownership semantics are
+  explicit where they cross the boundary.
+- Capability absence is visible to callers.
 - An ordinary interface is used unless `expect/actual` provides a concrete static benefit.
-- New abstractions have a real caller and reduce a measurable dependency.
-
-## Module graph
-
-- App host owns implementation aggregation, DI and navigation graph aggregation.
-- Core does not import Feature.
-- Feature API does not import another Feature.
-- Feature implementation uses other Feature APIs only.
-- Shared modules do not depend on platform implementations.
-- Platform implementations are injected from Koin/app composition roots.
-- Gradle `api` exposure is intentional; other dependencies use `implementation`.
-- No module is created only to match the target diagram.
-- Package colocation, Android Gradle module extraction, and KMP/CMP conversion are separate
-  reviewable stages; the slice does not combine all three by default.
-
-## Source sets and platform code
-
-- `commonMain` is compiled by at least one non-Android target.
-- JVM+Android-only sharing has an explicit source-set/module owner and verified IDE/consumer
-  behavior; it is not mislabeled as common.
-- Platform files are in the narrowest relevant source set.
-- Android services, Room/Context/URI/resources, notifications and renderer code stay Android-side.
-- Rhino/JS behavior stays behind a capability boundary until another target has a compatible
-  implementation.
-- Shared Compose code emits callbacks/effects; host navigation and platform launchers stay outside.
+- Every abstraction has a real caller and removes a measurable dependency.
+- Native or IPC exports are smaller and more stable than the internal Kotlin API.
+
+## Module and source-set graph
+
+- The app host owns implementation aggregation, DI, navigation/window lifecycle and packaging.
+- Core does not import Feature; Feature does not import another Feature implementation.
+- Shared modules do not depend on platform implementations or renderer modules.
+- Gradle `api` exposure is intentional; otherwise use `implementation`.
+- Package colocation, Android module extraction, KMP conversion and renderer sharing remain separate
+  changes unless evidence requires combining them.
+- `commonMain` is compiled by at least one non-Android target before being called shared.
+- JVM+Android-only sharing has an honest owner and is not mislabeled as common.
+- Platform files live in the narrowest relevant source set.
+- Android services, Context/URI/resources and notifications remain Android-side.
+- Rhino/JS behavior remains behind a capability boundary until another target has a compatible,
+  tested implementation.
+
+## Renderer and host checks
+
+- Presentation contracts contain no Compose, Material/Miuix, Fluent, WinUI, SwiftUI or platform
+  resource types.
+- A renderer owns its resources, accessibility, layout and navigation presentation.
+- Shared Compose UI emits semantic callbacks/effects; the host owns platform launchers.
+- A CMP Screen has at least two intentional consumers or another documented product reason.
+- WinUI/iOS bridges define versioning, lifecycle, cancellation, error and memory ownership.
+- A sidecar protocol defines startup, readiness, shutdown, crash recovery, upgrade and local-access
+  behavior.
 
 ## Gates
 
-- G0 Android test/lint/architecture/debug gates pass.
+- Affected Android unit/lint/architecture/package gates pass.
 - Common tests and actual metadata/target compile tasks pass.
-- Capability status distinguishes compile, contract-test, smoke, package, and release-ready
-  evidence.
-- Adapter contract tests cover success, failure and cancellation where relevant.
-- Serialization/database changes have forward/backward or migration evidence.
-- Reader/rule/service changes have parity and, when relevant, real-device performance evidence.
+- Capability status distinguishes compile, contract-test, smoke, package and release-ready.
+- Adapter tests cover success, failure and cancellation where relevant.
+- Serialization/database changes include backward/forward or migration evidence.
+- Reader/rule/service changes include parity and, where relevant, real-device performance evidence.
 - Reduced historical violations lower their baseline in the same change.
 - `git diff --check` passes.
 
 ## Scaffolding
 
-- At least two accepted manual examples prove the convention.
+- At least two accepted manual examples prove a convention before it is generated.
 - Dry-run is available and default execution refuses overwrites.
-- Existing graphs/DI are not replaced wholesale.
-- Only necessary files are generated; no empty layers.
-- Template fixtures or compile tests cover generator changes.
-- Generated committed source is reviewed like handwritten code.
+- Existing graph/DI files are amended safely rather than replaced wholesale.
+- Only necessary files are generated; no empty layers or speculative targets.
+- Generator behavior has fixture, snapshot or compilation evidence.
 
 ## Review output
 
-List findings by impact:
+For each finding give a tight file/line reference, concrete impact and smallest credible fix:
 
-- P0/P1: behavior/data loss, incompatible rule or storage semantics, broken actual,
-  lifecycle/thread/cancellation defects, reader regression.
-- P2: illegal dependency, platform leakage, false capability, state duplication, baseline
-  relaxation, or unnecessary cross-platform abstraction.
+- P0/P1: behavior/data loss, incompatible rule/storage/ABI semantics, broken target, lifecycle,
+  thread, cancellation, native ownership or packaging defects.
+- P2: illegal dependency, renderer/platform leakage, false capability, state duplication, baseline
+  relaxation or unnecessary abstraction.
 - P3: convention, naming, documentation, graph or template drift.
 
-For each finding, give a tight file/line reference, concrete impact, and smallest credible fix. If
-no finding exists, state remaining unverified platform, device or performance risks.
+If no issue is found, state the remaining unverified host, device, package, interop and performance
+risks.

+ 0 - 127
.claude/settings.local.json

@@ -1,127 +0,0 @@
-{
-  "permissions": {
-    "allow": [
-      "Bash(./gradlew compileAppDebugKotlin)",
-      "Bash(./gradlew test *)",
-      "Bash(./gradlew :app:compileAppDebugUnitTestKotlin)",
-      "Bash(head -n 508 \"app/src/main/java/io/legado/app/model/CacheBook.kt\")",
-      "Bash(mv /tmp/cachebook_tmp.kt \"app/src/main/java/io/legado/app/model/CacheBook.kt\")",
-      "Bash(./gradlew :app:cleanCompileAppDebugKotlin)",
-      "Bash(./gradlew :app:compileAppDebugKotlin --rerun-tasks)",
-      "Bash(./gradlew :app:compileAppDebugKotlin)",
-      "Bash(./gradlew :app:cleanCompileAppDebugKotlin :app:compileAppDebugKotlin)",
-      "Bash(cp -r \"D:/AndroidPrj/legado-with-MD3/.codex/skills/legado-compose-review/SKILL.md\" \"D:/AndroidPrj/legado-with-MD3/.claude/skills/legado-compose-review/SKILL.md\")",
-      "Bash(cp -r \"D:/AndroidPrj/legado-with-MD3/.codex/skills/legado-compose-review/references\" \"D:/AndroidPrj/legado-with-MD3/.claude/skills/legado-compose-review/\")",
-      "WebSearch",
-      "Bash(./gradlew assembleAppDebug)",
-      "Bash(git checkout *)",
-      "Bash(./gradlew :app:compileAppDebugKotlin --no-daemon)",
-      "Bash(Select-Object -Last 50)",
-      "PowerShell(./gradlew :app:compileAppDebugKotlin --no-daemon 2>&1 | Select-Object -Last 50)",
-      "PowerShell(./gradlew :app:compileAppDebugKotlin --rerun-tasks 2>&1 | Select-Object -Last 30)",
-      "PowerShell(./gradlew :app:compileAppDebugKotlin --rerun-tasks 2>&1 | Select-Object -Last 40)",
-      "PowerShell(./gradlew :app:compileAppDebugKotlin --rerun-tasks 2>&1 | Select-Object -Last 15)",
-      "PowerShell(./gradlew :app:compileAppDebugKotlin --rerun-tasks 2>&1 | Select-Object -Last 5)",
-      "Bash(./gradlew assembleAppNoR8)",
-      "Bash(.\\\\gradlew.bat :app:compileAppDebugKotlin)",
-      "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 30)",
-      "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 15)",
-      "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 10)",
-      "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 8)",
-      "Bash(Get-ChildItem \"app/src/main/java/io/legado/app/ui/main/bookshelf/\" -Name)",
-      "Bash(Sort-Object)",
-      "Bash(git rm *)",
-      "Bash(git commit *)",
-      "Bash(Select-Object -Last 30)",
-      "Bash(./gradlew.bat :app:compileAppDebugKotlin)",
-      "Bash(git add *)",
-      "Bash(git mv *)",
-      "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1)",
-      "WebFetch(domain:cs.android.com)",
-      "WebFetch(domain:github.com)",
-      "PowerShell(Get-ChildItem *)",
-      "PowerShell(Get-ChildItem -Path \"D:\\\\Android\\\\.gradle\\\\caches\\\\modules-2\\\\files-2.1\\\\androidx.navigation3\\\\navigation3-runtime-android\\\\1.0.1\" -Recurse | ForEach-Object { $_.FullName + \" \\(\" + $_.Length + \" bytes\\)\" })",
-      "Bash(jar tf *)",
-      "Read(//d/tmp/**)",
-      "Bash(jar xf *)",
-      "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin)",
-      "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 5)",
-      "PowerShell(.\\\\gradlew.bat test 2>&1 | Select-Object -Last 10)",
-      "Bash(npx skills *)",
-      "Bash(npx *)",
-      "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 20)",
-      "Bash(cmd /c \"gradlew.bat :app:compileAppDebugKotlin\")",
-      "Bash(cmd /c \"gradlew.bat :app:compileAppDebugKotlin 2>&1\")",
-      "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 40)",
-      "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 100)",
-      "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 50)",
-      "Bash(python3 *)",
-      "WebFetch(domain:raw.githubusercontent.com)",
-      "Bash(xargs -I{} jar -tf {})",
-      "Bash(gh api *)",
-      "Bash(rustup show *)",
-      "PowerShell(rustup show *)",
-      "PowerShell(npm *)",
-      "PowerShell(gcc *)",
-      "Bash(gh repo *)",
-      "Bash(Test-Path *)",
-      "Bash(git stash *)",
-      "Bash(Select-String -Pattern \"FAILED|error:\")",
-      "Bash(Select-Object -First 5)",
-      "Bash(Select-String -NotMatch \"^\\\\s*$\")",
-      "Bash(Select-Object -Last 20)",
-      "WebFetch(domain:compose-miuix-ui.github.io)",
-      "Bash(Select-Object -Last 60)",
-      "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 60)",
-      "Bash(New-Item *)",
-      "Bash(dir /s /b \"%USERPROFILE%\\\\.gradle\\\\caches\\\\*miuix*\")",
-      "Bash(findstr /i \"jar\\\\|aar\")",
-      "Bash(dir /s /b \"%USERPROFILE%\\\\.gradle\\\\caches\\\\transforms-*\\\\*PullToRefresh*\")",
-      "Bash(dir /s /b \"D:\\\\AndroidPrj\\\\legado-with-MD3\\\\app\\\\build\\\\*PullToRefresh*\")",
-      "Bash(dir /s /b \"D:\\\\AndroidPrj\\\\legado-with-MD3\")",
-      "Bash(findstr /i \"miuix.*\\\\.kt\")",
-      "Bash(findstr /v \"\\\\\\\\build\\\\\\\\\")",
-      "Bash(dir /s /b \"D:\\\\AndroidPrj\\\\legado-with-MD3\\\\.gradle\\\\*miuix*\")",
-      "Skill(legado-compose-review)",
-      "Bash(cmd /c \".\\\\gradlew.bat :app:compileAppDebugKotlin\")",
-      "PowerShell(gh search *)",
-      "WebFetch(domain:repo1.maven.org)",
-      "WebFetch(domain:s01.oss.sonatype.org)",
-      "WebFetch(domain:api.github.com)",
-      "Bash(javap -public /tmp/classes.jar!top/yukonga/miuix/kmp/basic/NavigationBarKt.class)",
-      "Bash(javap -public -cp /tmp/classes.jar top.yukonga.miuix.kmp.basic.NavigationBarKt)",
-      "PowerShell(Join-Path *)",
-      "Bash(dir /s /b \"C:\\\\Users\\\\*\\\\.gradle\\\\caches\")",
-      "Bash(findstr /i \"material3\")",
-      "Bash(findstr /i \"sources\")",
-      "Bash(dir /s /b \"%USERPROFILE%\\\\.gradle\\\\caches\")",
-      "Bash(findstr /i \"material3.*sources\")",
-      "WebFetch(domain:android.googlesource.com)",
-      "Read(//c/Users//**)",
-      "Bash(Get-ChildItem -Path \"D:\\\\AndroidPrj\\\\legado-with-MD3\" -Directory -Depth 0)",
-      "Bash(Select-Object Name)",
-      "PowerShell(Get-ChildItem -Path \"D:\\\\AndroidPrj\\\\legado-with-MD3\\\\app\\\\src\\\\main\\\\java\\\\io\\\\legado\\\\app\" -Directory -Depth 1 | ForEach-Object { $_.FullName.Replace\\(\"D:\\\\AndroidPrj\\\\legado-with-MD3\\\\app\\\\src\\\\main\\\\java\\\\io\\\\legado\\\\app\\\\\", \"\"\\) })",
-      "Bash(gh pr *)",
-      "PowerShell(Get-Content *)",
-      "Bash(python *)",
-      "Bash(C:\\\\Users\\\\www13\\\\.claude\\\\projects\\\\D--AndroidPrj-legado-with-MD3\\\\c1441549-f22e-480f-8eaf-d830b19148d1\\\\tool-results\\\\guangyu_source.json)",
-      "Bash(node -e ' *)",
-      "Bash(node *)",
-      "Bash(Select-String -Pattern \"BUILD|error|Error\")",
-      "Bash(Select-Object -First 10)",
-      "Bash(Select-Object -Last 10)",
-      "Bash(dir /s /b \"%USERPROFILE%\\\\.gradle\\\\caches\\\\*kyant*\")",
-      "Bash(Select-String -Pattern \"\\(BUILD|error:|FAILED\\)\")",
-      "PowerShell(grep *)",
-      "Bash(powershell -Command \"./gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 30\")",
-      "Bash(git revert *)",
-      "Bash(xargs grep -l \"ImeLazyListAutoScroller\")",
-      "Bash(xargs grep -l \"MarkdownBlock\")",
-      "Bash(./gradlew.bat assembleAppDebug)",
-      "Bash(./gradlew.bat test *)",
-      "Bash(./gradlew.bat :app:testAppDebugUnitTest --tests \"io.legado.app.data.repository.MangaSettingsMappingTest\" --tests \"io.legado.app.ui.book.manga.MangaReaderInteractionTest\")",
-      "Bash(./gradlew.bat :app:compileAppDebugKotlin -q)",
-      "Bash(./gradlew.bat :app:testDebugUnitTest --tests \"io.legado.app.data.repository.MangaSettingsMappingTest\" --tests \"io.legado.app.ui.book.manga.*\" -q)"
-    ]
-  }
-}

+ 0 - 73
.claude/skills/legado-compose-migration/SKILL.md

@@ -1,73 +0,0 @@
----
-name: legado-compose-migration
-description: Guide Legado Android UI migration from XML/View/RecyclerView/DialogFragment screens to Jetpack Compose and Material 3, and guide new Compose-first screens using standard Android architecture. Use when creating, migrating, rewriting, reviewing, or planning a Legado screen, Activity, Fragment, dialog, adapter, navigation destination, or settings page, especially when MainActivity navigation, MVI/UDF, StateFlow/SharedFlow, Koin ViewModels, domain/usecase boundaries, edge-to-edge insets, predictive back, or existing Compose component conventions matter.
----
-
-# Legado Compose Migration
-
-## Overview
-
-Migrate one UI surface at a time, or create one new Compose destination at a time. Preserve behavior first for migrations; for newly created screens, prefer standard modern Android architecture over mixed legacy patterns. Use the project's existing `MainActivity` navigation, `BaseComposeActivity` compatibility hosts, `*Screen`, `*Contract`, `*ViewModel`, `StateFlow`, `SharedFlow`, Koin, theme, and widget patterns.
-
-Before editing, inspect the target View implementation if one exists, `MainActivity` navigation when adding a destination, and at least two nearby migrated Compose screens. For concrete project patterns, including current Compose state/performance rules, read `references/project-patterns.md`.
-
-## Workflow
-
-1. State assumptions and success criteria.
-   - Name the exact screen or destination being created/migrated.
-   - For migrations, define behavior that must remain unchanged: inputs, result codes, navigation, menu actions, dialogs/sheets, list selection, refresh, persistence, and event bus behavior.
-   - For new screens, define the route owner, UI state, events/effects, domain/usecase boundary, and verification target.
-   - If the target mixes UI and business logic heavily, keep the migration surgical and defer deeper domain cleanup unless needed.
-
-2. Map the old surface.
-   - For migrations, read the Activity/Fragment, XML layouts, adapters, menu XML, dialogs, result launchers, and ViewModel.
-   - For new screens, read `MainActivity`, nearby route screens, the relevant ViewModel/usecase/repository patterns, and shared UI components.
-   - List UI state, user intents, one-shot effects, and external side effects.
-   - Identify reusable Compose components under `ui/widget/components` before creating new components.
-
-3. Choose the minimal migration shape.
-   - For new Compose-first screens, add a `MainActivity` navigation destination instead of creating a standalone Activity.
-   - Use `BaseComposeActivity` for full-screen Activity migrations only when a legacy Activity must remain as an entry point.
-   - Keep existing Activity Result APIs, `Intent` extras, permission flows, and Android framework calls in the host/compatibility Activity.
-   - If an unreworked View screen still starts the migrated screen with `Intent`, keep the old Activity only as a compatibility host that parses legacy extras and delegates to the Compose screen or `MainActivity` route boundary.
-   - Put renderable state in `UiState`, user actions in `Intent`, and one-shot navigation/framework work in `Effect`.
-   - For new screens, use standard Android/Compose architecture: UDF/MVI-style state hoisting, lifecycle-aware Flow collection, ViewModel-owned state, repository/usecase boundaries, and UI free of business logic.
-   - Ensure the new screen handles edge-to-edge correctly: use `Scaffold` (which respects `WindowInsets` automatically) or apply `Modifier.windowInsetsPadding(WindowInsets.safeDrawing)` on the outermost container. Do not carry over `fitsSystemWindows` / manual padding patterns from XML.
-   - Use mixed legacy patterns only as an integration boundary for unreworked View screens or existing framework contracts.
-   - Use existing repositories/usecases when they already fit; introduce new domain/usecase classes when a new screen needs clean business boundaries or when a migration would otherwise duplicate or entangle business logic.
-
-4. Implement by layers.
-   - Create or update `FeatureContract.kt` first for `UiState`, `Intent`, `Effect`, dialog/sheet models, and menu action enums.
-   - Update `FeatureViewModel.kt` to expose `uiState: StateFlow<UiState>` and `effects: SharedFlow<Effect>`, with a single `onIntent(...)` entry point unless the existing feature has a simpler established pattern.
-   - Create `FeatureScreen.kt` as a stateless route-level composable: `state`, callbacks, and `onIntent`.
-   - Wire new Compose destinations through `MainActivity` route handling; update a retained Activity only when legacy `Intent` compatibility is required.
-   - Collect state with `collectAsStateWithLifecycle()` and collect effects in `LaunchedEffect(Unit)` from the route or compatibility host.
-   - Register new ViewModels in `di/appModule.kt` with `viewModelOf(::FeatureViewModel)` unless parameters require `viewModel { ... }`.
-
-5. Remove only obsolete migration artifacts.
-   - Delete XML layouts, adapters, menu resources, binding fields, and imports only when the migrated screen no longer references them.
-   - Do not refactor unrelated View screens or shared utilities.
-
-6. Verify.
-   - Prefer the smallest Gradle check that compiles the touched app code, usually `.\gradlew.bat :app:compileAppDebugKotlin`.
-   - If resources, manifests, or XML deletion are involved, run `.\gradlew.bat :app:assembleAppDebug` when feasible.
-   - For behavior-heavy changes, add or update focused tests only where the project already has a practical test seam.
-
-## Boundaries
-
-- Keep Compose functions side-effect-light. Use `LaunchedEffect` for collecting effects and use callbacks for user actions.
-- Do not pass `Activity`, `View`, binding objects, or mutable domain entities deep into composables unless an existing local pattern requires it.
-- Prefer project theme/components: `LegadoTheme`, `AppScaffold`, `ListScaffold`, `AppAlertDialog`, `AppModalBottomSheet`, `RoundDropdownMenu`, top bar helpers, setting items, cover components, and list utilities.
-- Prefer `StateFlow`/`SharedFlow` over `LiveData` for newly migrated Compose surfaces.
-- Annotate `UiState` data classes and UI-facing model wrappers with `@Stable` when they hold collections or entity data passed to composables. Kotlin 2.x strong skipping is on by default, but explicit `@Stable` provides the strongest compiler guarantee and helps document contract boundaries.
-- When using `FeatureIntent` for MVI user actions, distinguish it from Android `Intent` extras and launch APIs in names, comments, and explanations where both appear.
-- For new Compose-first screens, do not copy View-era shortcuts such as UI logic in Activity/Fragment, direct binding-like mutable UI state, adapter-owned state, or Activity-context business operations.
-- Keep one-off Android actions out of `UiState`: navigation, file opening, clipboard, dialogs implemented as Android DialogFragments, result launchers, permission requests, and callbacks that require host context should be `Effect`s handled by `MainActivity` route handling or a compatibility Activity.
-- Prefer `MainActivity` navigation for new Compose destinations. Treat standalone Activities for migrated screens as legacy entry points only when existing View code still depends on `Intent` navigation.
-- For edge-to-edge: this project targets SDK 37, so edge-to-edge is enforced on Android 15+. Use Material 3 `Scaffold` insets or `WindowInsets.safeDrawing` / `safeContent` padding; ensure migrated screens draw behind system bars via `Modifier.windowInsetsPadding()` or top-level scaffold padding rather than manual hardcoded offsets. Migrated screens that relied on `fitsSystemWindows` in XML must be updated.
-- For predictive back: New Compose destinations should work with the predictive back gesture (enabled by default in Navigation 3). When a screen requires back confirmation (unsaved changes, selection mode), use `BackHandler` to intercept and route through `FeatureIntent.BackPressed`.
-- Keep existing Chinese string resources and localization behavior; add strings to resources when user-facing text is new.
-
-## Reference
-
-Read `references/project-patterns.md` when implementing or reviewing a migration. It contains project-specific examples, file placement rules, state/effect conventions, and verification commands.

+ 0 - 194
.claude/skills/legado-compose-migration/references/project-patterns.md

@@ -1,194 +0,0 @@
-# Legado Compose Migration Project Patterns
-
-## Existing Shape
-
-- App module: `app`.
-- Main source root: `app/src/main/java/io/legado/app`.
-- UI package: `ui/...`, usually grouped by feature.
-- Data package: `data/dao`, `data/entities`, `data/repository`.
-- Domain package: `domain/gateway`, `domain/model`, `domain/repository`, `domain/usecase`.
-- DI: `di/appModule.kt`, using Koin `singleOf`, `viewModelOf`, and parameterized `viewModel { ... }`.
-- Main navigation: `ui/main/MainActivity.kt`, using Navigation3 `NavKey`, `rememberNavBackStack`, `entryProvider`, and `NavDisplay`, plus legacy route extras such as `EXTRA_START_ROUTE`.
-- Base Compose host: `base/BaseComposeActivity.kt`, which wraps `Content()` in `AppTheme`, configures system bars, locale/font, background image, and LiveBus recreation events.
-- Compose dependencies are already enabled in `app/build.gradle.kts`; do not add new UI libraries unless the target screen truly requires one.
-- `kotlinx.collections.immutable` is available for Compose-facing screen state. Prefer `ImmutableList`, `ImmutableSet`, and `ImmutableMap` at `UiState` boundaries when collection state is passed to composables and changes often.
-
-## Files to Inspect Before Migrating
-
-Inspect these as local examples, not as APIs to copy blindly:
-
-- `ui/book/info/BookInfoActivity.kt`
-- `ui/book/info/BookInfoContract.kt`
-- `ui/book/info/BookInfoViewModel.kt`
-- `ui/book/info/BookInfoScreen.kt`
-- `ui/book/search/SearchContract.kt`
-- `ui/book/search/SearchViewModel.kt`
-- `ui/book/search/SearchScreen.kt`
-- `ui/main/MainActivity.kt`
-- `ui/main/MainScreen.kt`
-- `ui/widget/components/AppScaffold.kt`
-- `ui/widget/components/list/ListScaffold.kt`
-- `ui/theme/LegadoTheme.kt`
-- `ui/theme/AppTheme.kt`
-
-## Recommended Feature Layout
-
-For a migrated feature, prefer colocated files:
-
-```text
-ui/<area>/<feature>/
-  FeatureActivity.kt       # only for legacy Intent compatibility or full-screen migration host
-  FeatureContract.kt
-  FeatureViewModel.kt
-  FeatureScreen.kt
-  FeatureSheets.kt        # only if sheets are substantial
-  FeatureDialogs.kt       # only if dialogs are substantial
-```
-
-Use the smallest set of files. Do not split files just to match this shape.
-
-## MVI and UDF Rules
-
-Use this shape for behavior-heavy screens:
-
-```kotlin
-@Stable
-data class FeatureUiState(
-    val isLoading: Boolean = false,
-    val items: ImmutableList<Item> = persistentListOf(),
-    val dialog: FeatureDialog? = null,
-    val sheet: FeatureSheet = FeatureSheet.None,
-)
-
-sealed interface FeatureIntent {
-    data object BackPressed : FeatureIntent
-    data object Refresh : FeatureIntent
-    data class ItemClick(val id: Long) : FeatureIntent
-}
-
-sealed interface FeatureEffect {
-    data object Finish : FeatureEffect
-    data class OpenDetail(val id: Long) : FeatureEffect
-}
-```
-
-ViewModel rules:
-
-- Keep `_uiState = MutableStateFlow(FeatureUiState())` private and expose `uiState = _uiState.asStateFlow()`.
-- Keep `_effects = MutableSharedFlow<FeatureEffect>(extraBufferCapacity = 8)` private and expose `effects = _effects.asSharedFlow()`.
-- Prefer `fun onIntent(intent: FeatureIntent)` as the UI entry point.
-- Treat `FeatureIntent` as an MVI/user-action type. When Android `Intent` launch/extras are also involved, name variables and explanations clearly enough that the two concepts are not confused.
-- Use `_uiState.update { it.copy(...) }` for state changes.
-- Emit one-shot work through effects, not booleans in state.
-- Keep cached current entities private in the ViewModel when existing project behavior needs mutation or incremental sync, but publish immutable render state.
-- Annotate `UiState` data classes with `@Stable` to give the Compose compiler the strongest stability guarantee. This is especially important when `UiState` fields include collections or entity types that come from non-Compose modules.
-- For collection-heavy `UiState`, convert DAO/repository `List`/`Set`/`Map` values to `kotlinx.collections.immutable` at the ViewModel/UI-state boundary with `toImmutableList()`, `toImmutableSet()`, or `toImmutableMap()`. Do not force repository, DAO, or domain APIs to use persistent collections unless the domain contract truly benefits.
-- Keep internal flow pipelines free to use normal Kotlin collections for sorting, grouping, and persistence work; the immutable collection rule is primarily for values exposed to Compose.
-- Use existing `BaseViewModel.execute { ... }.onSuccess { ... }.onError { ... }` when the surrounding ViewModel already uses that pattern.
-
-Activity rules:
-
-- Prefer `MainActivity` as the owner of Compose-first navigation. Add a Navigation3 route/key and `entryProvider` entry there for new destinations.
-- Extend `BaseComposeActivity` for migrated Activity screens only when a standalone Activity remains necessary.
-- Keep a legacy Activity for a migrated screen only when unreworked View code still calls it through `Intent`; in that case, make the Activity a thin compatibility host that translates extras/results and delegates to Compose state/effects.
-- Keep Activity Result launchers, file openers, clipboard, permission APIs, Android DialogFragments, and framework navigation in the Activity.
-- In `Content()`, collect state with `collectAsStateWithLifecycle().value`.
-- Collect effects in `LaunchedEffect(Unit) { viewModel.effects.collectLatest { ... } }`.
-- Pass only `state` and callbacks into the screen.
-- Edge-to-edge: Since target SDK ≥ 35, edge-to-edge is enforced. In `BaseComposeActivity`, `enableEdgeToEdge()` is called before `setContent`. Ensure screen-level composables apply `Modifier.windowInsetsPadding(WindowInsets.safeDrawing)` on the outermost non-scaffold container, or rely on Material 3 `Scaffold` (which handles `WindowInsets` automatically). Do not use manual `statusBarsPadding()` / `navigationBarsPadding()` on top of scaffold-level insets — double-padding is a common pitfall.
-
-Navigation rules:
-
-- For new Compose-first destinations, add the route to `MainActivity` instead of adding a new Activity.
-- Follow `MainActivity`'s current route style: serializable `NavKey` routes for in-process navigation and launcher `Intent` helper methods only when external or legacy callers need a stable entry point.
-- Keep legacy extra names stable when replacing an old Activity entry point.
-- When a compatibility Activity is retained, let it translate old `Intent` inputs/results; do not make it the source of truth for navigation or business behavior.
-- Predictive back: Navigation 3 enables predictive back by default on Android 14+. New composable destinations work with it automatically. When a screen needs back confirmation (unsaved changes, selection mode, add-to-shelf prompt), use `BackHandler(enabled = ...) { onIntent(FeatureIntent.BackPressed) }` to intercept and let the ViewModel decide — don't bypass ViewModel back logic.
-
-Screen rules:
-
-- Make `FeatureScreen(state, onIntent, ...)` stateless for business state.
-- Use `remember` and `rememberSaveable` only for local UI affordances such as menu expansion, scroll state, transient animation state, and text field drafts when committing through an intent.
-- Use `rememberSaveable` for user-driven transient targets that should survive recreation, such as a delete-confirmation item ID or URL. Store only the stable ID in saved state and resolve the current entity from `UiState`.
-- Use `BackHandler` to route back actions through `FeatureIntent.BackPressed` when ViewModel decisions matter.
-- Keep expensive derived values in `remember(key)` or ViewModel state when they depend on repository data.
-- Prefer `LazyColumn`, `LazyRow`, stable keys, and existing fast-scroller/list components for adapter migrations. For heterogeneous lazy lists or grids, provide `contentType` in addition to `key`.
-- In long-lived `LaunchedEffect` collectors, wrap changing callbacks, `Context`-dependent operations, or lambdas from parents with `rememberUpdatedState` when the effect should not restart.
-- Hoist state only to the lowest common owner that reads and writes it. Move branch-only ViewModel lookup and Flow collection into the branch or a small child composable instead of collecting at a high-level route.
-- Avoid UI-state feedback loops such as `LaunchedEffect(uiState.items) { viewModel.pruneSelection(...) }`. Prefer deriving consistency inside the ViewModel with `combine(...)`, or reduce the state as part of the flow that produces the data.
-
-## Current Compose Performance Notes
-
-- Kotlin 2.x + Compose compiler plugin (`org.jetbrains.kotlin.plugin.compose`) enables **strong skipping by default**. This means composable functions with unstable parameters CAN still be skipped if their arguments are equal by `equals()`. However, ordinary Kotlin `List`, `Set`, and `Map` are still **unstable types** — passing them as parameters prevents the compiler from inferring stability, so changing a list reference still triggers recomposition of callers even with strong skipping.
-- Use `@Stable` on `UiState` data classes and UI model wrappers to give the Compose compiler the strongest stability guarantee. `@Stable` tells the compiler: "if `equals()` says two instances are the same, the UI hasn't changed." This is critical for data classes holding collections.
-- Use `kotlinx.collections.immutable` (`ImmutableList`, `ImmutableSet`, `ImmutableMap`) for render state that crosses into Compose. These are recognized as stable by the Compose compiler. Do not mechanically replace every temporary collection, Room query result, or internal mutable accumulator.
-- If data entities come from modules where the Compose compiler plugin is not applied, consider a `@Stable` UI model wrapper when the entity is passed deeply through composables and causes measurable recomposition cost.
-- Treat `SnapshotStateList` / `SnapshotStateMap` as UI-owned mutable state, not as a default ViewModel `UiState` transport type.
-
-## Clean Architecture Boundaries
-
-This project is mid-migration, so use Clean Architecture pragmatically. For newly created screens, default to standard modern Android architecture and avoid mixed legacy patterns unless they are needed to interoperate with View screens that have not been rewritten.
-
-- For new screens, keep UI, presentation, domain, and data boundaries explicit: Compose renders state, ViewModel owns state and intent handling, usecases hold reusable business actions, repositories mediate data access.
-- For new screens, do not place business rules in Activity/Fragment or composables.
-- For new screens, do not read DAOs directly from UI/presentation unless the project already has no reasonable repository/usecase boundary and adding one would be disproportionate.
-- For migrations, use existing DAOs/repositories directly if the old ViewModel already does and the migration is UI-only.
-- Prefer existing `domain/usecase` classes for reusable business actions such as cache, delete, group update, startup maintenance, reading progress, and WebDAV flows.
-- Add a usecase when a new screen has meaningful business rules, when logic would otherwise be duplicated across features, or when a UI migration needs to extract meaningful business rules from an Activity/adapter.
-- Keep UI-only formatting and Compose layout decisions out of domain.
-- Keep entity-to-display mapping near the UI or ViewModel unless it is reused business language.
-
-## Compose Components and Theme
-
-Prefer project components before raw Material widgets:
-
-- Scaffold/top bars: `AppScaffold`, `GlassTopAppBar...`, `DynamicTopAppBar`, `TopBarActionButton`, `TopBarNavigationButton`.
-- Lists: `ListScaffold`, `TopFloatingStickyItem`, `VerticalFastScroller`, `SelectionBottomBar`.
-- Dialog/sheet: `AppAlertDialog`, `AppModalBottomSheet`, `OptionSheet`, text list input components.
-- Settings: `ClickableSettingItem`, `SwitchSettingItem`, `SliderSettingItem`, `ListSettingItem`, `SettingCard`.
-- Buttons/chips: `AppIconButton`, `SmallIconButton`, `SmallTextButton`, `ToggleChip`, `AlertButton`.
-- Covers/images: `CoilBookCover`, `BookshelfCover`, `buildCoverImageRequest`; use Coil for Compose image loading.
-- Theme: `LegadoTheme.colorScheme`, `LegadoTheme.typography`, `ThemeResolver`, `ProvideThemeOverride` where existing feature behavior needs theme override.
-
-Avoid introducing a second visual system. If a raw Material 3 component is used, style it from `LegadoTheme`.
-
-## Migration Checklist
-
-Before editing:
-
-- Identify old XML layouts, ViewBinding fields, adapters, decorations, menu XML, dialogs, and result contracts.
-- Capture behavior: empty/loading/error states, refresh, search, sort, selection, long click, swipe, menu actions, back behavior, result codes, events, and persisted preferences.
-- Decide whether the task is UI-only or needs domain extraction.
-- For new screens, explicitly decide the presentation/domain/data boundary before coding; do not default to legacy mixed architecture.
-
-During editing:
-
-- Create/update `Contract`, then `ViewModel`, then `Screen`, then `MainActivity` route or compatibility host.
-- For new Compose destinations, wire navigation through `MainActivity` unless a legacy `Intent` caller must remain supported.
-- When retaining an Activity for compatibility, keep it small: parse legacy extras, collect effects, bridge result codes, and avoid placing feature business logic there.
-- Verify edge-to-edge: ensure the screen draws edge-to-edge without overlapping system bars. Use `Scaffold` or `Modifier.windowInsetsPadding(WindowInsets.safeDrawing)`. Remove any `fitsSystemWindows`-based margin hacks from the old layout.
-- Reuse existing string resources and add new resources for new user-facing text.
-- Register the ViewModel in `di/appModule.kt`.
-- Remove only resources made obsolete by this migration.
-- Keep existing public intent extra names and result codes unless explicitly changing an API.
-
-Verification:
-
-- Run `.\gradlew.bat :app:compileAppDebugKotlin` for Kotlin-only changes.
-- Run `.\gradlew.bat :app:assembleAppDebug` when resources, manifests, XML deletion, or generated bindings are affected.
-- If a screen uses Room queries or migration-sensitive repositories, run the relevant existing Android/unit tests if practical.
-- For manual verification, open the migrated screen and compare core flows against the old behavior list.
-
-## Common Pitfalls
-
-- Do not put navigation or Activity launchers inside composables.
-- Do not store one-time navigation as nullable fields in `UiState` unless the existing local pattern already does; prefer `SharedFlow` effects.
-- Do not keep adapter selection state split between old adapter and new Compose state.
-- Do not delete XML/menu resources until all references and generated binding imports are gone.
-- Do not add speculative domain layers for a single migrated screen.
-- Do not use mixed architecture for new screens unless compatibility with unreworked View UI requires it.
-- Do not make a migrated screen's retained Activity the primary navigation model when `MainActivity` can own the Compose destination.
-- Do not bypass `BaseComposeActivity` unless the screen must remain inside a Fragment or legacy host.
-- Do not replace existing event bus or service interactions as part of a UI migration unless required for correctness.
-- Do not forget edge-to-edge: XML `fitsSystemWindows` does not carry over to Compose. Rely on `Scaffold` or `Modifier.windowInsetsPadding()` instead.
-- Do not forget `@Stable` on `UiState` data classes — missing annotations cause unnecessary recomposition, especially when state contains collections.

+ 0 - 61
.claude/skills/legado-compose-review/SKILL.md

@@ -1,61 +0,0 @@
----
-name: legado-compose-review
-description: Review existing Legado Jetpack Compose code for architecture, behavior, maintainability, and project convention issues. Use when auditing, reviewing, inspecting, evaluating, or finding problems in Legado Compose screens, routes, ViewModels, contracts, dialogs, sheets, navigation, or early Compose implementations, especially for MVI/UDF, StateFlow/SharedFlow, Clean Architecture, MainActivity navigation, legacy Activity compatibility, edge-to-edge insets, @Stable stability annotations, predictive back, and View-era mixed-pattern drift.
----
-
-# Legado Compose Review
-
-## Overview
-
-Review existing Compose code before rewriting it. Focus on concrete defects, architectural drift, behavior risks, and missing verification, especially in early Compose screens that may predate the current MVI/UDF, Clean Architecture, and `MainActivity` navigation expectations.
-
-Read `references/review-checklist.md` for the project-specific checklist and severity guidance.
-
-## Workflow
-
-1. Define the review scope.
-   - Identify the exact screen, route, ViewModel, contract, or package under review.
-   - State whether the user wants review only or review plus fixes. If unclear, review first and do not edit code.
-   - Treat unrelated legacy View code as context, not as part of the review, unless it affects the Compose surface.
-
-2. Build context from code.
-   - Read the `*Screen`, `*ViewModel`, `*Contract`, host Activity/route, DI registration, and any repositories/usecases used by the feature.
-   - Read the old View implementation only if it is still a compatibility caller or behavior reference.
-   - Compare against nearby current examples such as `BookInfo*`, `Search*`, `MainActivity`, and shared components under `ui/widget/components`.
-
-3. Review by risk, not style preference.
-   - Prioritize behavior regressions, state duplication, lifecycle bugs, navigation bugs, business logic in UI, direct data access from UI/presentation, recomposition hazards, and missing compatibility handling.
-   - Flag style-only issues only when they conflict with established project conventions or make future migration harder.
-   - Do not require broad refactors for a small screen unless the current code creates real behavior or maintenance risk.
-
-4. Output findings first.
-   - Use code-review style: list findings before summaries.
-   - Include file and tight line references.
-   - Explain the concrete impact and the smallest credible fix.
-   - If using Codex review directives, emit one `::code-comment{...}` per finding.
-   - If no issues are found, say that clearly and mention remaining test/manual verification gaps.
-
-5. Suggest fixes only after findings.
-   - Group fixes into small, reviewable steps.
-   - For architecture drift, separate compatibility-preserving fixes from larger cleanup.
-   - Recommend `legado-compose-migration` only when the finding implies a migration or rewrite workflow.
-
-## Review Priorities
-
-- **P0/P1**: behavior breakage, lost navigation/result behavior, unsafe lifecycle collection, stale state, data corruption, crashes, compatibility entry points that no longer work, or content obscured by system bars (edge-to-edge breakage).
-- **P2**: architecture drift that will cause duplicated logic, hard-to-test behavior, recomposition bugs, incorrect ownership of state/effects, or missing `@Stable` annotations that cause measurable recomposition.
-- **P3**: convention drift, maintainability issues, weak naming, missing previews/tests, or small cleanup that should not block behavior.
-
-## Boundaries
-
-- Do not rewrite code during a review unless the user asks for fixes.
-- Do not demand pure architecture where a compatibility boundary is necessary for unreworked View screens.
-- For new Compose-first code, expect standard Android architecture: `MainActivity` route ownership, ViewModel-owned state, UDF/MVI user actions, `StateFlow`/`SharedFlow`, repositories/usecases, and UI without business logic.
-- For migrated code, allow a thin retained Activity only for Android `Intent` compatibility with unreworked View callers.
-- Distinguish MVI `FeatureIntent` user actions from Android `Intent` launch/extras when both appear.
-- For edge-to-edge: expect new and migrated Compose screens to handle system bar insets correctly — `Scaffold` or `Modifier.windowInsetsPadding()`, not hardcoded padding or XML `fitsSystemWindows` hacks. Target SDK 37 enforces edge-to-edge on Android 15+.
-- For stability: expect `@Stable` on `UiState` data classes and UI model wrappers. Flag unannotated state classes that hold collections or entity types from non-Compose modules, as they cause unnecessary recomposition.
-
-## Reference
-
-Read `references/review-checklist.md` for detailed checks. When a review turns into implementation, also read `../legado-compose-migration/references/project-patterns.md` if available.

+ 0 - 117
.claude/skills/legado-compose-review/references/review-checklist.md

@@ -1,117 +0,0 @@
-# Legado Compose Review Checklist
-
-## Files to Read
-
-For a feature review, inspect the smallest complete slice:
-
-- `FeatureScreen.kt` and any `FeatureSheets.kt` / `FeatureDialogs.kt`.
-- `FeatureViewModel.kt`.
-- `FeatureContract.kt` if present.
-- Host route in `MainActivity.kt` or retained `FeatureActivity.kt`.
-- Koin registration in `di/appModule.kt`.
-- Repositories/usecases used by the feature.
-- Old View caller or XML/adapters only when still used for compatibility or behavior comparison.
-
-Use current examples as references:
-
-- `ui/main/MainActivity.kt` for Navigation3 route ownership.
-- `ui/book/info/BookInfoContract.kt`, `BookInfoViewModel.kt`, `BookInfoScreen.kt` for behavior-heavy MVI/UDF.
-- `ui/book/search/SearchContract.kt`, `SearchViewModel.kt`, `SearchScreen.kt` for route-level search state.
-- `ui/widget/components/...` and `ui/theme/...` for shared UI conventions.
-
-## Architecture Checks
-
-Flag issues when:
-
-- A new Compose-first screen is implemented as a standalone Activity instead of a `MainActivity` destination without a compatibility reason.
-- A retained Activity contains feature business logic instead of acting as a thin Android `Intent` compatibility host.
-- Business rules, repository calls, DAO calls, persistence writes, or service orchestration live in composables.
-- ViewModel exposes mutable state directly, exposes `MutableStateFlow`, or lets UI mutate domain objects.
-- UI state is split across Activity fields, composable `remember` state, adapter state, and ViewModel state in a way that can diverge.
-- One-shot events such as navigation, result codes, file opening, permission requests, or clipboard writes are represented as persistent `UiState` fields that can replay incorrectly.
-- MVI `FeatureIntent` user actions are confused with Android `Intent` launch/extras.
-- Domain/usecase boundaries are bypassed in new screens when a meaningful business action exists.
-- A migration introduces new domain abstractions for a single trivial UI action without reducing real complexity.
-
-## UDF and State Checks
-
-Flag issues when:
-
-- `Screen` functions own business state instead of receiving `state` and callbacks.
-- `remember` / `rememberSaveable` stores source-of-truth data that should survive process or route recreation through ViewModel state.
-- `LaunchedEffect` keys are unstable or cause repeated data loading, duplicate navigation, duplicate toasts, or repeated service calls.
-- Flows are collected without lifecycle awareness in UI routes where `collectAsStateWithLifecycle()` should be used.
-- List items lack stable keys where mutation, selection, or animation can make state attach to the wrong row.
-- Selection, search query, sorting, filtering, loading, and error states are not represented in a single coherent `UiState`.
-- Derived values are recomputed expensively on every recomposition instead of living in ViewModel state or `remember(key)`.
-
-## Navigation and Compatibility Checks
-
-Flag issues when:
-
-- New Compose destinations skip `MainActivity` Navigation3 route registration.
-- Legacy Android `Intent` extras or result codes change without an explicit compatibility plan.
-- A migrated screen is reachable both through `MainActivity` and a retained Activity with inconsistent state initialization.
-- Navigation is performed directly inside nested composables instead of through callbacks/effects.
-- Activity Result launchers, file pickers, permission requests, or Android DialogFragments are hidden inside reusable UI composables.
-- Back behavior bypasses ViewModel decisions when unsaved changes, selection mode, add-to-shelf prompts, or confirmation dialogs exist.
-
-## UI and Project Convention Checks
-
-Flag issues when:
-
-- Raw Material components ignore `LegadoTheme` or existing shared components where the project already has a wrapper.
-- A screen recreates shared UI already present under `ui/widget/components`.
-- User-facing text is hardcoded instead of using string resources, except for temporary debug-only text.
-- Compose UI keeps ViewBinding, adapter, or XML assumptions after migration.
-- Dialogs and bottom sheets use inconsistent project components when `AppAlertDialog`, `AppModalBottomSheet`, or existing option sheets fit.
-- Image loading bypasses existing Coil cover/image helpers where cover behavior, cache, SVG, or GIF handling matters.
-
-## Clean Architecture Checks
-
-For new screens, expect:
-
-- Compose renders state and emits user actions.
-- ViewModel owns state, intent handling, and effect emission.
-- Usecases own reusable business actions.
-- Repositories mediate data access.
-- DAOs remain behind repositories unless adding a boundary would be disproportionate and local project patterns already allow direct use.
-
-For migrated screens, allow pragmatic intermediate code only when:
-
-- It preserves behavior.
-- It is isolated behind a clear compatibility boundary.
-- It does not spread View-era assumptions into new Compose-first code.
-
-## Output Template
-
-Use this shape unless the user asks for another format:
-
-```text
-Findings
-- [P1] Title
-  file:line
-  Impact: ...
-  Fix: ...
-
-Open Questions
-- ...
-
-Notes
-- No issues found in ... / Tests not run ...
-```
-
-When using Codex inline review comments, emit one directive per finding with tight line ranges:
-
-```text
-::code-comment{title="[P2] Keep route state in ViewModel" body="..." file="/absolute/path/FeatureScreen.kt" start=42 end=45 priority=2 confidence=0.8}
-```
-
-## Verification Suggestions
-
-Recommend verification based on the reviewed change:
-
-- Kotlin-only review/fix: `.\gradlew.bat :app:compileAppDebugKotlin`.
-- Resource/XML/manifest impact: `.\gradlew.bat :app:assembleAppDebug`.
-- Navigation or compatibility changes: manually open both `MainActivity` route and any retained legacy Android `Intent` entry point.
-- Behavior-heavy ViewModel changes: add or run focused tests only where the project has a practical test seam.