{"owner":"maxrave-dev","repo":"SimpMusic","hasSkills":true,"totalSkillsCount":66,"totalTokensCount":36606,"categories":["anthropic-skill","claude-rule","subagent-persona"],"hasMcp":false,"mcpConfig":null,"found":[".claude/skills/kotlin-concurrency-expert/SKILL.md",".claude/skills/marketing-ideas/SKILL.md",".claude/skills/xml-to-compose-migration/SKILL.md",".claude/skills/jetpack-compose/SKILL.md",".claude/skills/simpmusic-icons/SKILL.md",".claude/skills/kmp/SKILL.md",".claude/skills/compose-ui/SKILL.md",".claude/skills/gradle-build-performance/SKILL.md",".claude/skills/compose-performance-audit/SKILL.md",".claude/skills/mobile-android-design/SKILL.md",".claude/skills/find-skills/SKILL.md",".claude/skills/compose-navigation/SKILL.md",".claude/skills/coil-compose/SKILL.md",".claude/skills/android-viewmodel/SKILL.md",".claude/skills/android-testing/SKILL.md",".claude/skills/android-retrofit/SKILL.md",".claude/skills/android-gradle-logic/SKILL.md",".claude/skills/android-emulator-skill/SKILL.md",".claude/skills/android-data-layer/SKILL.md",".claude/skills/android-coroutines/SKILL.md",".claude/skills/android-architecture/SKILL.md",".claude/skills/android-accessibility/SKILL.md","CLAUDE.md",".claude/agents/mobile-developer.md",".claude/skills/ui-ux-pro-max/scripts/core.py",".claude/skills/ui-ux-pro-max/scripts/search.py",".claude/skills/ui-ux-pro-max/data/charts.csv",".claude/skills/ui-ux-pro-max/data/colors.csv",".claude/skills/ui-ux-pro-max/data/icons.csv",".claude/skills/ui-ux-pro-max/data/landing.csv",".claude/skills/ui-ux-pro-max/data/products.csv",".claude/skills/ui-ux-pro-max/data/prompts.csv",".claude/skills/ui-ux-pro-max/data/styles.csv",".claude/skills/ui-ux-pro-max/data/typography.csv",".claude/skills/ui-ux-pro-max/data/ux-guidelines.csv",".claude/skills/mobile-android-design/references/android-navigation.md",".claude/skills/mobile-android-design/references/compose-components.md",".claude/skills/mobile-android-design/references/material3-theming.md",".claude/skills/marketing-ideas/references/ideas-by-category.md",".claude/skills/kmp/scripts/suggest-kmp-dependency.sh",".claude/skills/kmp/scripts/validate-kmp-structure.sh",".claude/skills/kmp/references/abstraction-examples.md",".claude/skills/kmp/references/expect-actual-catalog.md",".claude/skills/kmp/references/source-set-hierarchy.md",".claude/skills/kmp/references/target-compatibility.md",".claude/skills/android-emulator-skill/scripts/app_launcher.py",".claude/skills/android-emulator-skill/scripts/build_and_test.py",".claude/skills/android-emulator-skill/scripts/common.py",".claude/skills/android-emulator-skill/scripts/emu_health_check.sh",".claude/skills/android-emulator-skill/scripts/emulator_manage.py",".claude/skills/android-emulator-skill/scripts/gesture.py",".claude/skills/android-emulator-skill/scripts/keyboard.py",".claude/skills/android-emulator-skill/scripts/log_monitor.py",".claude/skills/android-emulator-skill/scripts/navigator.py",".claude/skills/android-emulator-skill/scripts/screen_mapper.py",".claude/skills/ui-ux-pro-max/data/stacks/flutter.csv",".claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv",".claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv",".claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv",".claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv",".claude/skills/ui-ux-pro-max/data/stacks/react.csv",".claude/skills/ui-ux-pro-max/data/stacks/react-native.csv",".claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv",".claude/skills/ui-ux-pro-max/data/stacks/svelte.csv",".claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv",".claude/skills/ui-ux-pro-max/data/stacks/vue.csv"],"skills":{".claude/skills/kotlin-concurrency-expert/SKILL.md":"---\nname: kotlin-concurrency-expert\ndescription: Kotlin Coroutines review and remediation for Android. Use when asked to review concurrency usage, fix coroutine-related bugs, improve thread safety, or resolve lifecycle issues in Kotlin/Android code.\n---\n\n# Kotlin Concurrency Expert\n\n## Overview\n\nReview and fix Kotlin Coroutines issues in Android codebases by applying structured concurrency, lifecycle safety, proper scoping, and modern best practices with minimal behavior changes.\n\n## Workflow\n\n### 1. Triage the Issue\n\n- Capture the exact error, crash, or symptom (ANR, memory leak, race condition, incorrect state).\n- Check project coroutines setup: `kotlinx-coroutines-android` version, `lifecycle-runtime-ktx` version.\n- Identify the current scope context (`viewModelScope`, `lifecycleScope`, custom scope, or none).\n- Confirm whether the code is UI-bound (`Dispatchers.Main`) or intended to run off the main thread (`Dispatchers.IO`, `Dispatchers.Default`).\n- Verify Dispatcher injection patterns for testability.\n\n### 2. Apply the Smallest Safe Fix\n\nPrefer edits that preserve existing behavior while satisfying structured concurrency and lifecycle safety.\n\nCommon fixes:\n\n- **ANR / Main thread blocking**: Move heavy work to `withContext(Dispatchers.IO)` or `Dispatchers.Default`; ensure suspend functions are main-safe.\n- **Memory leaks / zombie coroutines**: Replace `GlobalScope` with a lifecycle-bound scope (`viewModelScope`, `lifecycleScope`, or injected `applicationScope`).\n- **Lifecycle collection issues**: Replace deprecated `launchWhenStarted` with `repeatOnLifecycle(Lifecycle.State.STARTED)`.\n- **State exposure**: Encapsulate `MutableStateFlow` / `MutableSharedFlow`; expose read-only `StateFlow` or `Flow`.\n- **CancellationException swallowing**: Ensure generic `catch (e: Exception)` blocks rethrow `CancellationException`.\n- **Non-cooperative cancellation**: Add `ensureActive()` or `yield()` in tight loops for cooperative cancellation.\n- **Callback APIs**: Convert listeners to `callbackFlow` with proper `awaitClose` cleanup.\n- **Hardcoded Dispatchers**: Inject `CoroutineDispatcher` via constructor for testability.\n\n## Critical Rules\n\n### Dispatcher Injection (Testability)\n\n```kotlin\n// CORRECT: Inject dispatcher\nclass UserRepository(\n    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO\n) {\n    suspend fun fetchUser() = withContext(ioDispatcher) { ... }\n}\n\n// INCORRECT: Hardcoded dispatcher\nclass UserRepository {\n    suspend fun fetchUser() = withContext(Dispatchers.IO) { ... }\n}\n```\n\n### Lifecycle-Aware Collection\n\n```kotlin\n// CORRECT: Use repeatOnLifecycle\nviewLifecycleOwner.lifecycleScope.launch {\n    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {\n        viewModel.uiState.collect { state -> updateUI(state) }\n    }\n}\n\n// INCORRECT: Direct collection (unsafe, deprecated)\nlifecycleScope.launchWhenStarted {\n    viewModel.uiState.collect { state -> updateUI(state) }\n}\n```\n\n### State Encapsulation\n\n```kotlin\n// CORRECT: Expose read-only StateFlow\nclass MyViewModel : ViewModel() {\n    private val _uiState = MutableStateFlow(UiState())\n    val uiState: StateFlow<UiState> = _uiState.asStateFlow()\n}\n\n// INCORRECT: Exposed mutable state\nclass MyViewModel : ViewModel() {\n    val uiState = MutableStateFlow(UiState()) // Leaks mutability\n}\n```\n\n### Exception Handling\n\n```kotlin\n// CORRECT: Rethrow CancellationException\ntry {\n    doSuspendWork()\n} catch (e: CancellationException) {\n    throw e // Must rethrow!\n} catch (e: Exception) {\n    handleError(e)\n}\n\n// INCORRECT: Swallows cancellation\ntry {\n    doSuspendWork()\n} catch (e: Exception) {\n    handleError(e) // CancellationException swallowed!\n}\n```\n\n### Cooperative Cancellation\n\n```kotlin\n// CORRECT: Check for cancellation in tight loops\nsuspend fun processLargeList(items: List<Item>) {\n    items.forEach { item ->\n        ensureActive() // Check cancellation\n        processItem(item)\n    }\n}\n\n// INCORRECT: Non-cooperative (ignores cancellation)\nsuspend fun processLargeList(items: List<Item>) {\n    items.forEach { item ->\n        processItem(item) // Never checks cancellation\n    }\n}\n```\n\n### Callback Conversion\n\n```kotlin\n// CORRECT: callbackFlow with awaitClose\nfun locationUpdates(): Flow<Location> = callbackFlow {\n    val listener = LocationListener { location ->\n        trySend(location)\n    }\n    locationManager.requestLocationUpdates(listener)\n    \n    awaitClose { locationManager.removeUpdates(listener) }\n}\n```\n\n## Scope Guidelines\n\n| Scope | Use When | Lifecycle |\n|-------|----------|-----------|\n| `viewModelScope` | ViewModel operations | Cleared with ViewModel |\n| `lifecycleScope` | UI operations in Activity/Fragment | Destroyed with lifecycle owner |\n| `repeatOnLifecycle` | Flow collection in UI | Started/Stopped with lifecycle state |\n| `applicationScope` (injected) | App-wide background work | Application lifetime |\n| `GlobalScope` | **NEVER USE** | Breaks structured concurrency |\n\n## Testing Pattern\n\n```kotlin\n@Test\nfun `loading data updates state`() = runTest {\n    val testDispatcher = StandardTestDispatcher(testScheduler)\n    val repository = FakeRepository()\n    val viewModel = MyViewModel(repository, testDispatcher)\n    \n    viewModel.loadData()\n    advanceUntilIdle()\n    \n    assertEquals(UiState.Success(data), viewModel.uiState.value)\n}\n```\n\n## Reference Material\n\n- [Kotlin Coroutines Best Practices](https://developer.android.com/kotlin/coroutines/coroutines-best-practices)\n- [StateFlow and SharedFlow](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow)\n- [repeatOnLifecycle API](https://developer.android.com/topic/libraries/architecture/coroutines#repeatOnLifecycle)\n",".claude/skills/marketing-ideas/SKILL.md":"---\nname: marketing-ideas\nversion: 1.0.0\ndescription: \"When the user needs marketing ideas, inspiration, or strategies for their SaaS or software product. Also use when the user asks for 'marketing ideas,' 'growth ideas,' 'how to market,' 'marketing strategies,' 'marketing tactics,' 'ways to promote,' or 'ideas to grow.' This skill provides 139 proven marketing approaches organized by category.\"\n---\n\n# Marketing Ideas for SaaS\n\nYou are a marketing strategist with a library of 139 proven marketing ideas. Your goal is to help users find the right marketing strategies for their specific situation, stage, and resources.\n\n## How to Use This Skill\n\n**Check for product marketing context first:**\nIf `.claude/product-marketing-context.md` exists, read it before asking questions. Use that context and only ask for information not already covered or specific to this task.\n\nWhen asked for marketing ideas:\n1. Ask about their product, audience, and current stage if not clear\n2. Suggest 3-5 most relevant ideas based on their context\n3. Provide details on implementation for chosen ideas\n4. Consider their resources (time, budget, team size)\n\n---\n\n## Ideas by Category (Quick Reference)\n\n| Category | Ideas | Examples |\n|----------|-------|----------|\n| Content & SEO | 1-10 | Programmatic SEO, Glossary marketing, Content repurposing |\n| Competitor | 11-13 | Comparison pages, Marketing jiu-jitsu |\n| Free Tools | 14-22 | Calculators, Generators, Chrome extensions |\n| Paid Ads | 23-34 | LinkedIn, Google, Retargeting, Podcast ads |\n| Social & Community | 35-44 | LinkedIn audience, Reddit marketing, Short-form video |\n| Email | 45-53 | Founder emails, Onboarding sequences, Win-back |\n| Partnerships | 54-64 | Affiliate programs, Integration marketing, Newsletter swaps |\n| Events | 65-72 | Webinars, Conference speaking, Virtual summits |\n| PR & Media | 73-76 | Press coverage, Documentaries |\n| Launches | 77-86 | Product Hunt, Lifetime deals, Giveaways |\n| Product-Led | 87-96 | Viral loops, Powered-by marketing, Free migrations |\n| Content Formats | 97-109 | Podcasts, Courses, Annual reports, Year wraps |\n| Unconventional | 110-122 | Awards, Challenges, Guerrilla marketing |\n| Platforms | 123-130 | App marketplaces, Review sites, YouTube |\n| International | 131-132 | Expansion, Price localization |\n| Developer | 133-136 | DevRel, Certifications |\n| Audience-Specific | 137-139 | Referrals, Podcast tours, Customer language |\n\n**For the complete list with descriptions**: See [references/ideas-by-category.md](references/ideas-by-category.md)\n\n---\n\n## Implementation Tips\n\n### By Stage\n\n**Pre-launch:**\n- Waitlist referrals (#79)\n- Early access pricing (#81)\n- Product Hunt prep (#78)\n\n**Early stage:**\n- Content & SEO (#1-10)\n- Community (#35)\n- Founder-led sales (#47)\n\n**Growth stage:**\n- Paid acquisition (#23-34)\n- Partnerships (#54-64)\n- Events (#65-72)\n\n**Scale:**\n- Brand campaigns\n- International (#131-132)\n- Media acquisitions (#73)\n\n### By Budget\n\n**Free:**\n- Content & SEO\n- Community building\n- Social media\n- Comment marketing\n\n**Low budget:**\n- Targeted ads\n- Sponsorships\n- Free tools\n\n**Medium budget:**\n- Events\n- Partnerships\n- PR\n\n**High budget:**\n- Acquisitions\n- Conferences\n- Brand campaigns\n\n### By Timeline\n\n**Quick wins:**\n- Ads, email, social posts\n\n**Medium-term:**\n- Content, SEO, community\n\n**Long-term:**\n- Brand, thought leadership, platform effects\n\n---\n\n## Top Ideas by Use Case\n\n### Need Leads Fast\n- Google Ads (#31) - High-intent search\n- LinkedIn Ads (#28) - B2B targeting\n- Engineering as Marketing (#15) - Free tool lead gen\n\n### Building Authority\n- Conference Speaking (#70)\n- Book Marketing (#104)\n- Podcasts (#107)\n\n### Low Budget Growth\n- Easy Keyword Ranking (#1)\n- Reddit Marketing (#38)\n- Comment Marketing (#44)\n\n### Product-Led Growth\n- Viral Loops (#93)\n- Powered By Marketing (#87)\n- In-App Upsells (#91)\n\n### Enterprise Sales\n- Investor Marketing (#133)\n- Expert Networks (#57)\n- Conference Sponsorship (#72)\n\n---\n\n## Output Format\n\nWhen recommending ideas, provide for each:\n\n- **Idea name**: One-line description\n- **Why it fits**: Connection to their situation\n- **How to start**: First 2-3 implementation steps\n- **Expected outcome**: What success looks like\n- **Resources needed**: Time, budget, skills required\n\n---\n\n## Task-Specific Questions\n\n1. What's your current stage and main growth goal?\n2. What's your marketing budget and team size?\n3. What have you already tried that worked or didn't?\n4. What competitor tactics do you admire?\n\n---\n\n## Related Skills\n\n- **programmatic-seo**: For scaling SEO content (#4)\n- **competitor-alternatives**: For comparison pages (#11)\n- **email-sequence**: For email marketing tactics\n- **free-tool-strategy**: For engineering as marketing (#15)\n- **referral-program**: For viral growth (#93)\n",".claude/skills/xml-to-compose-migration/SKILL.md":"---\nname: xml-to-compose-migration\ndescription: Convert Android XML layouts to Jetpack Compose. Use when asked to migrate Views to Compose, convert XML to Composables, or modernize UI from View system to Compose.\n---\n\n# XML to Compose Migration\n\n## Overview\n\nSystematically convert Android XML layouts to idiomatic Jetpack Compose, preserving functionality while embracing Compose patterns. This skill covers layout mapping, state migration, and incremental adoption strategies.\n\n## Workflow\n\n### 1. Analyze the XML Layout\n\n- Identify the root layout type (`ConstraintLayout`, `LinearLayout`, `FrameLayout`, etc.).\n- List all View widgets and their key attributes.\n- Map data binding expressions (`@{}`) or view binding references.\n- Identify custom views that need special handling.\n- Note any `include`, `merge`, or `ViewStub` usage.\n\n### 2. Plan the Migration\n\n- Decide: **Full rewrite** or **incremental migration** (using `ComposeView`/`AndroidView`).\n- Identify state sources (ViewModel, LiveData, savedInstanceState).\n- List reusable components to extract as separate Composables.\n- Plan navigation integration if using Navigation component.\n\n### 3. Convert Layouts\n\nApply the layout mapping table below to convert each View to its Compose equivalent.\n\n### 4. Migrate State\n\n- Convert `LiveData` observation to `StateFlow` collection or `observeAsState()`.\n- Replace `findViewById` / ViewBinding with Compose state.\n- Convert click listeners to lambda parameters.\n\n### 5. Test and Verify\n\n- Compare visual output between XML and Compose versions.\n- Test accessibility (content descriptions, touch targets).\n- Verify state preservation across configuration changes.\n\n---\n\n## Layout Mapping Reference\n\n### Container Layouts\n\n| XML Layout | Compose Equivalent | Notes |\n|------------|-------------------|-------|\n| `LinearLayout (vertical)` | `Column` | Use `Arrangement` and `Alignment` |\n| `LinearLayout (horizontal)` | `Row` | Use `Arrangement` and `Alignment` |\n| `FrameLayout` | `Box` | Children stack on top of each other |\n| `ConstraintLayout` | `ConstraintLayout` (Compose) | Use `createRefs()` and `constrainAs` |\n| `RelativeLayout` | `Box` or `ConstraintLayout` | Prefer Box for simple overlap |\n| `ScrollView` | `Column` + `Modifier.verticalScroll()` | Or use `LazyColumn` for lists |\n| `HorizontalScrollView` | `Row` + `Modifier.horizontalScroll()` | Or use `LazyRow` for lists |\n| `RecyclerView` | `LazyColumn` / `LazyRow` / `LazyGrid` | Most common migration |\n| `ViewPager2` | `HorizontalPager` | From accompanist or Compose Foundation |\n| `CoordinatorLayout` | Custom + `Scaffold` | Use `TopAppBar` with scroll behavior |\n| `NestedScrollView` | `Column` + `Modifier.verticalScroll()` | Prefer Lazy variants |\n\n### Common Widgets\n\n| XML Widget | Compose Equivalent | Notes |\n|------------|-------------------|-------|\n| `TextView` | `Text` | Use `style` → `TextStyle` |\n| `EditText` | `TextField` / `OutlinedTextField` | Requires state hoisting |\n| `Button` | `Button` | Use `onClick` lambda |\n| `ImageView` | `Image` | Use `painterResource()` or Coil |\n| `ImageButton` | `IconButton` | Use `Icon` inside |\n| `CheckBox` | `Checkbox` | Requires `checked` + `onCheckedChange` |\n| `RadioButton` | `RadioButton` | Use with `Row` for groups |\n| `Switch` | `Switch` | Requires state hoisting |\n| `ProgressBar (circular)` | `CircularProgressIndicator` | |\n| `ProgressBar (horizontal)` | `LinearProgressIndicator` | |\n| `SeekBar` | `Slider` | Requires state hoisting |\n| `Spinner` | `DropdownMenu` + `ExposedDropdownMenuBox` | More complex pattern |\n| `CardView` | `Card` | From Material 3 |\n| `Toolbar` | `TopAppBar` | Use inside `Scaffold` |\n| `BottomNavigationView` | `NavigationBar` | Material 3 |\n| `FloatingActionButton` | `FloatingActionButton` | Use inside `Scaffold` |\n| `Divider` | `HorizontalDivider` / `VerticalDivider` | |\n| `Space` | `Spacer` | Use `Modifier.size()` |\n\n### Attribute Mapping\n\n| XML Attribute | Compose Modifier/Property |\n|---------------|--------------------------|\n| `android:layout_width=\"match_parent\"` | `Modifier.fillMaxWidth()` |\n| `android:layout_height=\"match_parent\"` | `Modifier.fillMaxHeight()` |\n| `android:layout_width=\"wrap_content\"` | `Modifier.wrapContentWidth()` (usually implicit) |\n| `android:layout_weight` | `Modifier.weight(1f)` |\n| `android:padding` | `Modifier.padding()` |\n| `android:layout_margin` | `Modifier.padding()` on parent, or use `Arrangement.spacedBy()` |\n| `android:background` | `Modifier.background()` |\n| `android:visibility=\"gone\"` | Conditional composition (don't emit) |\n| `android:visibility=\"invisible\"` | `Modifier.alpha(0f)` (keeps space) |\n| `android:clickable` | `Modifier.clickable { }` |\n| `android:contentDescription` | `Modifier.semantics { contentDescription = \"\" }` |\n| `android:elevation` | `Modifier.shadow()` or component's `elevation` param |\n| `android:alpha` | `Modifier.alpha()` |\n| `android:rotation` | `Modifier.rotate()` |\n| `android:scaleX/Y` | `Modifier.scale()` |\n| `android:gravity` | `Alignment` parameter or `Arrangement` |\n| `android:layout_gravity` | `Modifier.align()` |\n\n---\n\n## Common Patterns\n\n### LinearLayout with Weights\n\n```xml\n<!-- XML -->\n<LinearLayout android:orientation=\"horizontal\">\n    <View android:layout_weight=\"1\" />\n    <View android:layout_weight=\"2\" />\n</LinearLayout>\n```\n\n```kotlin\n// Compose\nRow(modifier = Modifier.fillMaxWidth()) {\n    Box(modifier = Modifier.weight(1f))\n    Box(modifier = Modifier.weight(2f))\n}\n```\n\n### RecyclerView to LazyColumn\n\n```xml\n<!-- XML -->\n<androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/recyclerView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />\n```\n\n```kotlin\n// Compose\nLazyColumn(modifier = Modifier.fillMaxSize()) {\n    items(items, key = { it.id }) { item ->\n        ItemRow(item = item, onClick = { onItemClick(item) })\n    }\n}\n```\n\n### EditText with Two-Way Binding\n\n```xml\n<!-- XML with Data Binding -->\n<EditText\n    android:text=\"@={viewModel.username}\"\n    android:hint=\"@string/username_hint\" />\n```\n\n```kotlin\n// Compose\nval username by viewModel.username.collectAsState()\n\nOutlinedTextField(\n    value = username,\n    onValueChange = { viewModel.updateUsername(it) },\n    label = { Text(stringResource(R.string.username_hint)) },\n    modifier = Modifier.fillMaxWidth()\n)\n```\n\n### ConstraintLayout Migration\n\n```xml\n<!-- XML -->\n<androidx.constraintlayout.widget.ConstraintLayout>\n    <TextView\n        android:id=\"@+id/title\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\" />\n    <TextView\n        android:id=\"@+id/subtitle\"\n        app:layout_constraintTop_toBottomOf=\"@id/title\"\n        app:layout_constraintStart_toStartOf=\"@id/title\" />\n</androidx.constraintlayout.widget.ConstraintLayout>\n```\n\n```kotlin\n// Compose\nConstraintLayout(modifier = Modifier.fillMaxWidth()) {\n    val (title, subtitle) = createRefs()\n    \n    Text(\n        text = \"Title\",\n        modifier = Modifier.constrainAs(title) {\n            top.linkTo(parent.top)\n            start.linkTo(parent.start)\n        }\n    )\n    Text(\n        text = \"Subtitle\", \n        modifier = Modifier.constrainAs(subtitle) {\n            top.linkTo(title.bottom)\n            start.linkTo(title.start)\n        }\n    )\n}\n```\n\n### Include / Merge → Extract Composable\n\n```xml\n<!-- XML: layout_header.xml -->\n<merge>\n    <ImageView android:id=\"@+id/avatar\" />\n    <TextView android:id=\"@+id/name\" />\n</merge>\n\n<!-- Usage -->\n<include layout=\"@layout/layout_header\" />\n```\n\n```kotlin\n// Compose: Extract as a reusable Composable\n@Composable\nfun HeaderSection(\n    avatarUrl: String,\n    name: String,\n    modifier: Modifier = Modifier\n) {\n    Row(modifier = modifier) {\n        AsyncImage(model = avatarUrl, contentDescription = null)\n        Text(text = name)\n    }\n}\n\n// Usage\nHeaderSection(avatarUrl = user.avatar, name = user.name)\n```\n\n---\n\n## Incremental Migration (Interop)\n\n### Embedding Compose in XML\n\n```xml\n<!-- In your XML layout -->\n<androidx.compose.ui.platform.ComposeView\n    android:id=\"@+id/compose_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\" />\n```\n\n```kotlin\n// In Fragment/Activity\nbinding.composeView.setContent {\n    MaterialTheme {\n        MyComposable()\n    }\n}\n```\n\n### Embedding XML Views in Compose\n\n```kotlin\n// Use AndroidView for Views that don't have Compose equivalents\n@Composable\nfun MapViewComposable(modifier: Modifier = Modifier) {\n    AndroidView(\n        factory = { context ->\n            MapView(context).apply {\n                // Initialize the view\n            }\n        },\n        update = { mapView ->\n            // Update the view when state changes\n        },\n        modifier = modifier\n    )\n}\n```\n\n---\n\n## State Migration\n\n### LiveData to Compose\n\n```kotlin\n// Before: Observing in Fragment\nviewModel.uiState.observe(viewLifecycleOwner) { state ->\n    binding.title.text = state.title\n}\n\n// After: Collecting in Compose\n@Composable\nfun MyScreen(viewModel: MyViewModel = hiltViewModel()) {\n    val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n    \n    Text(text = uiState.title)\n}\n```\n\n### Click Listeners\n\n```kotlin\n// Before: XML + setOnClickListener\nbinding.submitButton.setOnClickListener {\n    viewModel.submit()\n}\n\n// After: Lambda in Compose\nButton(onClick = { viewModel.submit() }) {\n    Text(\"Submit\")\n}\n```\n\n---\n\n## Checklist\n\n- [ ] All layouts converted (no `include` or `merge` left)\n- [ ] State hoisted properly (no internal mutable state for user input)\n- [ ] Click handlers converted to lambdas\n- [ ] RecyclerView adapters removed (using LazyColumn/LazyRow)\n- [ ] ViewBinding/DataBinding removed\n- [ ] Navigation integrated (NavHost or interop)\n- [ ] Theming applied (MaterialTheme)\n- [ ] Accessibility preserved (content descriptions, touch targets)\n- [ ] Preview annotations added for development\n- [ ] Old XML files deleted\n\n## References\n\n- [Interoperability APIs](https://developer.android.com/develop/ui/compose/migrate/interoperability-apis)\n- [Migration Strategy](https://developer.android.com/develop/ui/compose/migrate/strategy)\n- [Compose and Views side by side](https://developer.android.com/develop/ui/compose/migrate)\n",".claude/skills/jetpack-compose/SKILL.md":"---\nname: android-jetpack-compose\ndescription: Use when building Android UIs with Jetpack Compose, managing state with remember/mutableStateOf, or implementing declarative UI patterns.\nallowed-tools:\n  - Read\n  - Write\n  - Edit\n  - Bash\n  - Grep\n  - Glob\n---\n\n# Android - Jetpack Compose\n\nModern declarative UI toolkit for building native Android interfaces.\n\n## Key Concepts\n\n### State Management\n\nCompose provides several ways to manage state:\n\n- **remember**: Survives recomposition\n- **rememberSaveable**: Survives configuration changes\n- **mutableStateOf**: Creates observable state\n- **derivedStateOf**: Computed state that updates when dependencies change\n\n```kotlin\n@Composable\nfun Counter() {\n    var count by remember { mutableStateOf(0) }\n\n    Column {\n        Text(\"Count: $count\")\n        Button(onClick = { count++ }) {\n            Text(\"Increment\")\n        }\n    }\n}\n\n// With saveable for configuration changes\n@Composable\nfun SearchField() {\n    var query by rememberSaveable { mutableStateOf(\"\") }\n\n    TextField(\n        value = query,\n        onValueChange = { query = it },\n        placeholder = { Text(\"Search...\") }\n    )\n}\n```\n\n### State Hoisting\n\nLift state up to make composables stateless and reusable:\n\n```kotlin\n// Stateless composable\n@Composable\nfun NameInput(\n    name: String,\n    onNameChange: (String) -> Unit,\n    modifier: Modifier = Modifier\n) {\n    TextField(\n        value = name,\n        onValueChange = onNameChange,\n        label = { Text(\"Name\") },\n        modifier = modifier\n    )\n}\n\n// Stateful parent\n@Composable\nfun UserForm() {\n    var name by remember { mutableStateOf(\"\") }\n\n    NameInput(\n        name = name,\n        onNameChange = { name = it }\n    )\n}\n```\n\n### ViewModel Integration\n\n```kotlin\nclass UserViewModel : ViewModel() {\n    private val _uiState = MutableStateFlow(UserUiState())\n    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()\n\n    fun updateName(name: String) {\n        _uiState.update { it.copy(name = name) }\n    }\n\n    fun saveUser() {\n        viewModelScope.launch {\n            _uiState.update { it.copy(isLoading = true) }\n            try {\n                userRepository.save(_uiState.value.toUser())\n                _uiState.update { it.copy(isLoading = false, isSaved = true) }\n            } catch (e: Exception) {\n                _uiState.update { it.copy(isLoading = false, error = e.message) }\n            }\n        }\n    }\n}\n\n@Composable\nfun UserScreen(viewModel: UserViewModel = viewModel()) {\n    val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n\n    UserContent(\n        uiState = uiState,\n        onNameChange = viewModel::updateName,\n        onSave = viewModel::saveUser\n    )\n}\n```\n\n## Best Practices\n\n### Composable Function Guidelines\n\n```kotlin\n// Use Modifier as first optional parameter\n@Composable\nfun CustomCard(\n    title: String,\n    modifier: Modifier = Modifier,\n    onClick: () -> Unit = {}\n) {\n    Card(\n        modifier = modifier.clickable(onClick = onClick)\n    ) {\n        Text(\n            text = title,\n            modifier = Modifier.padding(16.dp)\n        )\n    }\n}\n\n// Use slot APIs for flexible content\n@Composable\nfun CustomScaffold(\n    topBar: @Composable () -> Unit = {},\n    bottomBar: @Composable () -> Unit = {},\n    content: @Composable (PaddingValues) -> Unit\n) {\n    Scaffold(\n        topBar = topBar,\n        bottomBar = bottomBar,\n        content = content\n    )\n}\n```\n\n### Efficient Recomposition\n\n```kotlin\n// Use keys for list items\n@Composable\nfun UserList(users: List<User>) {\n    LazyColumn {\n        items(\n            items = users,\n            key = { it.id }  // Stable key for efficient updates\n        ) { user ->\n            UserItem(user)\n        }\n    }\n}\n\n// Use derivedStateOf for expensive computations\n@Composable\nfun FilteredList(items: List<Item>, query: String) {\n    val filteredItems by remember(items, query) {\n        derivedStateOf {\n            items.filter { it.name.contains(query, ignoreCase = true) }\n        }\n    }\n\n    LazyColumn {\n        items(filteredItems) { item ->\n            ItemRow(item)\n        }\n    }\n}\n```\n\n### Side Effects\n\n```kotlin\n// LaunchedEffect for coroutine-based side effects\n@Composable\nfun UserProfile(userId: String, viewModel: UserViewModel) {\n    LaunchedEffect(userId) {\n        viewModel.loadUser(userId)\n    }\n\n    // UI content\n}\n\n// DisposableEffect for cleanup\n@Composable\nfun LifecycleAwareComponent(lifecycle: Lifecycle) {\n    DisposableEffect(lifecycle) {\n        val observer = LifecycleEventObserver { _, event ->\n            // Handle lifecycle events\n        }\n        lifecycle.addObserver(observer)\n\n        onDispose {\n            lifecycle.removeObserver(observer)\n        }\n    }\n}\n\n// SideEffect for non-suspend side effects\n@Composable\nfun AnalyticsScreen(screenName: String) {\n    SideEffect {\n        analytics.logScreenView(screenName)\n    }\n}\n```\n\n## Common Patterns\n\n### Navigation with Navigation Compose\n\n```kotlin\n@Composable\nfun AppNavigation() {\n    val navController = rememberNavController()\n\n    NavHost(navController = navController, startDestination = \"home\") {\n        composable(\"home\") {\n            HomeScreen(\n                onNavigateToDetail = { id ->\n                    navController.navigate(\"detail/$id\")\n                }\n            )\n        }\n        composable(\n            route = \"detail/{itemId}\",\n            arguments = listOf(navArgument(\"itemId\") { type = NavType.StringType })\n        ) { backStackEntry ->\n            val itemId = backStackEntry.arguments?.getString(\"itemId\")\n            DetailScreen(itemId = itemId)\n        }\n    }\n}\n```\n\n### Material 3 Theming\n\n```kotlin\n@Composable\nfun AppTheme(\n    darkTheme: Boolean = isSystemInDarkTheme(),\n    content: @Composable () -> Unit\n) {\n    val colorScheme = when {\n        darkTheme -> darkColorScheme(\n            primary = Purple80,\n            secondary = PurpleGrey80,\n            tertiary = Pink80\n        )\n        else -> lightColorScheme(\n            primary = Purple40,\n            secondary = PurpleGrey40,\n            tertiary = Pink40\n        )\n    }\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        typography = Typography,\n        content = content\n    )\n}\n\n// Using theme values\n@Composable\nfun ThemedCard() {\n    Card(\n        colors = CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surfaceVariant\n        )\n    ) {\n        Text(\n            text = \"Themed content\",\n            style = MaterialTheme.typography.bodyLarge,\n            color = MaterialTheme.colorScheme.onSurfaceVariant\n        )\n    }\n}\n```\n\n### Lists and Grids\n\n```kotlin\n@Composable\nfun ProductGrid(products: List<Product>) {\n    LazyVerticalGrid(\n        columns = GridCells.Adaptive(minSize = 160.dp),\n        contentPadding = PaddingValues(16.dp),\n        horizontalArrangement = Arrangement.spacedBy(16.dp),\n        verticalArrangement = Arrangement.spacedBy(16.dp)\n    ) {\n        items(products, key = { it.id }) { product ->\n            ProductCard(product)\n        }\n    }\n}\n\n// Sticky headers\n@Composable\nfun ContactList(contacts: Map<Char, List<Contact>>) {\n    LazyColumn {\n        contacts.forEach { (initial, contactsForInitial) ->\n            stickyHeader {\n                Text(\n                    text = initial.toString(),\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .background(MaterialTheme.colorScheme.surface)\n                        .padding(16.dp),\n                    style = MaterialTheme.typography.titleMedium\n                )\n            }\n            items(contactsForInitial) { contact ->\n                ContactItem(contact)\n            }\n        }\n    }\n}\n```\n\n## Anti-Patterns\n\n### Avoid Side Effects in Composition\n\nBad:\n\n```kotlin\n@Composable\nfun BadExample(viewModel: ViewModel) {\n    viewModel.loadData()  // Called on every recomposition!\n\n    Text(\"Data loaded\")\n}\n```\n\nGood:\n\n```kotlin\n@Composable\nfun GoodExample(viewModel: ViewModel) {\n    LaunchedEffect(Unit) {\n        viewModel.loadData()\n    }\n\n    Text(\"Data loaded\")\n}\n```\n\n### Don't Read State in Remember Block\n\nBad:\n\n```kotlin\n@Composable\nfun BadCounter(initial: Int) {\n    // Won't update when initial changes\n    var count by remember { mutableStateOf(initial) }\n}\n```\n\nGood:\n\n```kotlin\n@Composable\nfun GoodCounter(initial: Int) {\n    var count by remember(initial) { mutableStateOf(initial) }\n}\n```\n\n### Avoid Heavy Computation During Composition\n\nBad:\n\n```kotlin\n@Composable\nfun BadList(items: List<Item>) {\n    // Runs on every recomposition\n    val sorted = items.sortedBy { it.name }\n    LazyColumn { /* ... */ }\n}\n```\n\nGood:\n\n```kotlin\n@Composable\nfun GoodList(items: List<Item>) {\n    val sorted by remember(items) {\n        derivedStateOf { items.sortedBy { it.name } }\n    }\n    LazyColumn { /* ... */ }\n}\n```\n\n## Related Skills\n\n- **android-architecture**: MVVM and clean architecture patterns\n- **android-kotlin-coroutines**: Async operations in Compose\n",".claude/skills/simpmusic-icons/SKILL.md":"---\nname: simpmusic-icons\ndescription: Add or change an icon in SimpMusic. Every icon is a generated Material Symbols ImageVector under ui/icon, addressed as SimpIcons.<Name> — there is no material-icons dependency and no XML icon drawable. Use when adding a new icon, replacing one, or hitting ImageVector/Painter type errors.\n---\n\n# SimpMusic Icons\n\nIcons live in `composeApp/src/commonMain/kotlin/com/maxrave/simpmusic/ui/icon/`, one file per\nicon, each declaring `val SimpIcons.<Name>: ImageVector`.\n\nTwo things are deliberately absent and must not come back:\n- the `material-icons-core` / `material-icons-extended` dependency (`Icons.Rounded.*`)\n- XML icon drawables in `composeResources/drawable` used through `painterResource(...)`\n\n## Adding an icon\n\nGoogle's font service generates the Compose file directly — no conversion tool needed:\n\n```bash\ncurl -sfL --compressed \\\n  \"https://fonts.gstatic.com/render/v1/Material+Symbols+Rounded/24dp/<symbol_name>.kt?var=opsz,wght,FILL,GRAD,ROND@24,400,1,0,50\" \\\n  -o <PascalName>.kt\n```\n\nKeep these axes identical for every icon, or the set stops looking like one set:\n**Rounded, opsz 24, wght 400, GRAD 0, ROND 50**, `FILL=1`.\n\nUse `FILL=0` **only** for the \"off\" half of a state pair — `FavoriteBorder`,\n`AddCircleOutline`, `DownloadForOfflineOutlined`. At `FILL=1` the off state renders identically to\nthe on state and the distinction is lost.\n\nThen edit the downloaded file:\n\n1. `package com.example.test` → `package com.maxrave.simpmusic.ui.icon`\n2. `public val <symbol_name>: ImageVector` → `val SimpIcons.<PascalName>: ImageVector`\n3. Rename the backing field `_<symbol_name>` → `_<PascalName>`, and `name = \"<symbol_name>\"` → `\"<PascalName>\"`\n4. Never add `autoMirror = true` to `ImageVector.Builder(...)`: in right-to-left languages only text\n   flips, icons keep their shape (owner's rule, 2026-10-14). `ArrowForwardIos`, `QueueMusic`, `Sort`,\n   `TrendingUp`, `VolumeOff` and `VolumeUp` had it and drew mirrored until it was removed.\n5. Replace the closing `.build()` with `.buildSimpIcon()` (in `SimpIcons.kt`): it is `build()` plus\n   the fit into Material's 20×20 live area described under Traps. `SimpIconsFitTest` fails on a\n   file that still ends with `.build()`.\n\n## Using an icon\n\n```kotlin\nimport com.maxrave.simpmusic.ui.icon.PlayArrow   // per-icon import, required\nimport com.maxrave.simpmusic.ui.icon.SimpIcons\n\nIcon(SimpIcons.PlayArrow, contentDescription = null)\n```\n\nThe project's own icon buttons take an `ImageVector`:\n`RippleIconButton(imageVector = …)`, `LiquidGlassIconButton(imageVector = …)`,\n`ActionButton(icon = …)`.\n\n## Traps\n\n**Every icon needs its own import.** `val SimpIcons.X` is an *extension property*; importing the\n`SimpIcons` object alone leaves it unresolved. This is the same mechanism that lets R8 strip unused\nicons, so never \"simplify\" the set into a `map` or a `when` — that would ship all of them.\n\n**`ImageVector` is not a `Painter`.** `Icon` and `Image` have overloads for both, so those look\nfine. These do not, and need `rememberVectorPainter(SimpIcons.X)`:\n- `AsyncImage(placeholder = …, error = …)`\n- anything drawn inside a `DrawScope` (`with(painter) { draw(...) }`)\n- custom composables whose parameter is typed `Painter`\n\n**Do not text-replace blindly.** A regex swap of `Res.drawable.X` → `SimpIcons.X` produced 40\ncompile errors in one pass: it also hit `Painter` parameters, and it landed *inside* multi-line\n`painterResource(\\n  Res.drawable.X,\\n)` calls, leaving the syntactically valid but wrong\n`painterResource(SimpIcons.X)`.\n\n**Do not replace an icon whose colour carries meaning.** These stay as resources:\n\n| File | Why |\n|---|---|\n| `baseline_downloaded.xml` | `#FF00A0CB` — the blue *is* the \"downloaded\" state |\n| `baseline_favorite_24.xml` | `#D10000` — the red *is* the \"liked\" state |\n| `mono.xml`, `monochrome.xml` | SimpMusic logos (`#0AEAF8` / black) |\n| `app_icon.png`, `circle_app_icon.png`, `holder*.png` | bitmaps, not icons |\n\nIf a state needs that colour with a shared symbol, pass it explicitly — e.g. `ActionButton`'s\n`iconColor = Color(0xFF00A0CB)` for the downloaded state.\n\n**Verify the symbol name exists** before assuming a mapping, against\n`google/material-design-icons` → `variablefont/MaterialSymbolsRounded[FILL,GRAD,opsz,wght].codepoints`.\nLegacy names such as `favorite_border`, `add_circle_outline` and `thumb_up_alt` *do* still exist;\n`person_add_alt_1` does not (use `person_add`).\n\n**Glyphs that spill past Material's 20×20 live area are fitted into it (2026-10-18).** Material's\nicon grid keeps a glyph inside the centred 20×20 square of the 24 box, with 2dp of padding, and\nbalances the glyphs inside it by keyline shapes of about equal area. 24 of our 111 Symbols spill\ninto that padding anyway: `Lyrics`' note reaches the box edge, `ThumbsUpDown` spans the\nfull width. At one size those read bigger than everything beside them. `buildSimpIcon()` measures\nhow far each glyph reaches from the centre along either axis (walking its path nodes, 16 samples\nper curve) and shrinks one that passes 10 of 24 about the centre until it fits: `Lyrics` and\n`ThumbsUpDown` ×0.83, the other 21 ×0.86–0.996. A glyph inside the square is left exactly as Google\ndrew it, nothing is enlarged, and anything it cannot measure (an arc, a group, a fit tighter than\n×0.6) keeps the glyph as generated and logs under `SimpIcons`. `SimpIconsFitTest` (jvmTest) fails if\na file skips `buildSimpIcon()`, an icon outside `KEEP_AS_DRAWN` ends up outside the square, or one in\nit gets rebuilt.\n- `Groups` is named in `KEEP_AS_DRAWN` (in `SimpIcons.kt`) and keeps Google's drawing (2026-10-20).\n  It spills only sideways, so fitting its 24 width also cut its height, which was never too tall,\n  from 12 to 10 of 24, and it read shorter than every icon beside it. Five more spill only sideways\n  and lose a little height to the fit (`PeopleAlt`, `PersonAdd`, `Insights` 9%, `AutoGraph` 6%,\n  `AccessAlarm` 2%); they still end 14.5 of 24 tall or more, like Symbols Google draws at that\n  height, so they stay fitted (owner's call).\n- Do not fit glyphs into a circle instead. That was tried first: a circle judges a glyph by its one\n  farthest point, so `Lyrics` shrank to ×0.72 to pull its note's tip inside and then read far\n  smaller than `QueueMusic` beside it.\n\n**A small round button cuts a glyph that fills the live area.** `IconButton` clips its content to\nits `shape` (a circle by default), and a button sized 24dp makes that a 24dp circle, 12dp from the\ncentre. A glyph that fills the 20×20 square reaches 14.1 at its corners — `comment` reaches 13.3 —\nso its corners are cut while round or open glyphs beside it are not. The icon file is not the\nproblem; the button is. Check the farthest glyph point before putting an icon in one: Classic's\nComments button clips to 28dp with `requiredSize(28.dp)` inside the same 24dp slot.\n\n**Icons in one row take one size.** Tuning each glyph by eye is what made the Apple Music row read\nbig-small (23 and 24dp beside 22dp dock buttons, the title-row star 32dp beside 24dp glyphs). Only a\ndeliberate hierarchy, such as a Play button over Previous/Next, gets a different size.\n\n**The response is gzipped** even when the request asks for `identity` — `curl --compressed`, or\ndecompress by magic bytes (`\\x1f\\x8b`) when fetching from a script.\n",".claude/skills/kmp/SKILL.md":"---\nname: kotlin-multiplatform\ndescription: |\n  Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific,\n  source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets\n  (Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues.\n  Triggers on: abstraction decisions (\"should I share this?\"), source set placement questions, expect/actual creation,\n  build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.\n---\n\n# Kotlin Multiplatform: Platform Abstraction Decisions\n\nExpert guidance for KMP architecture in Amethyst - deciding what to share vs keep platform-specific.\n\n## When to Use This Skill\n\nMaking platform abstraction decisions:\n- \"Should I create expect/actual or keep Android-only?\"\n- \"Can I share this ViewModel logic?\"\n- \"Where does this crypto/JSON/network implementation belong?\"\n- \"This uses Android Context - can it be abstracted?\"\n- \"Is this code in the wrong module?\"\n- Preparing for iOS/web/wasm targets\n- Detecting incorrect placements\n\n## Abstraction Decision Tree\n\n**Central question:** \"Should this code be reused across platforms?\"\n\nFollow this decision path (< 1 minute):\n\n```\nQ: Is it used by 2+ platforms?\n├─ NO  → Keep platform-specific\n│         Example: Android-only permission handling\n│\n└─ YES → Continue ↓\n\nQ: Is it pure Kotlin (no platform APIs)?\n├─ YES → commonMain\n│         Example: Nostr event parsing, business rules\n│\n└─ NO  → Continue ↓\n\nQ: Does it vary by platform or by JVM vs non-JVM?\n├─ By platform (Android ≠ iOS ≠ Desktop)\n│  → expect/actual\n│  Example: Secp256k1Instance (uses different security APIs)\n│\n├─ By JVM (Android = Desktop ≠ iOS/web)\n│  → jvmAndroid\n│  Example: Jackson JSON parsing (JVM library)\n│\n└─ Complex/UI-related\n   → Keep platform-specific\n   Example: Navigation (Activity vs Window too different)\n\nFinal check:\nQ: Maintenance cost of abstraction < duplication cost?\n├─ YES → Proceed with abstraction\n└─ NO  → Duplicate (simpler)\n```\n\n### Real Examples from Codebase\n\n**Crypto → expect/actual:**\n```kotlin\n// commonMain - expect declaration\nexpect object Secp256k1Instance {\n    fun signSchnorr(data: ByteArray, privKey: ByteArray): ByteArray\n}\n\n// androidMain - uses Android Keystore\n// jvmMain - uses Desktop JVM crypto\n// iosMain - uses iOS Security framework\n```\n**Why:** Each platform has different security APIs.\n\n**JSON parsing → jvmAndroid:**\n```kotlin\n// quartz/build.gradle.kts\nval jvmAndroid = create(\"jvmAndroid\") {\n    api(libs.jackson.module.kotlin)\n}\n```\n**Why:** Jackson is JVM-only, works on Android + Desktop, not iOS/web.\n\n**Navigation → platform-specific:**\n- Android: `MainActivity` (Activity + Compose Navigation)\n- Desktop: `Window` + sidebar + MenuBar\n**Why:** UI paradigms fundamentally different.\n\n## Mental Model: Source Sets as Dependency Graph\n\nThink of source sets as a dependency graph, not folders.\n\n```\n┌─────────────────────────────────────────────┐\n│ commonMain = Contract (pure Kotlin)         │\n│ - Business logic, protocol, data models     │\n│ - No platform APIs                          │\n└────────────┬────────────────────────────────┘\n             │\n             ├──────────────────────┬────────────────────\n             │                      │\n             ▼                      ▼\n   ┌───────────────────┐  ┌──────────────────┐\n   │ jvmAndroid        │  │ iosMain          │\n   │ JVM libs shared   │  │ iOS common       │\n   │ - Jackson         │  │                  │\n   │ - OkHttp          │  └────┬─────────────┘\n   └───┬───────────┬───┘       │\n       │           │            ├─→ iosX64Main\n       ▼           ▼            ├─→ iosArm64Main\n  ┌─────────┐ ┌──────────┐     └─→ iosSimulatorArm64Main\n  │android  │ │jvmMain   │\n  │Main     │ │(Desktop) │\n  └─────────┘ └──────────┘\n\nFuture: jsMain, wasmMain\n```\n\n**Key insight:** jvmAndroid is NOT a platform - it's a shared JVM layer.\n\n## The jvmAndroid Pattern\n\n**Unique to Amethyst.** Shares JVM libraries between Android + Desktop.\n\n### When to Use jvmAndroid\n\nUse jvmAndroid when:\n- ✅ JVM-specific libraries (Jackson, OkHttp, url-detector)\n- ✅ Android implementation = Desktop implementation (same JVM)\n- ✅ Library doesn't work on iOS/web\n\nDo NOT use jvmAndroid for:\n- ❌ Pure Kotlin code (use commonMain)\n- ❌ Platform-specific APIs (use androidMain/jvmMain)\n- ❌ Code that should work on all platforms\n\n### Example from quartz/build.gradle.kts\n\n```kotlin\n// Must be defined BEFORE androidMain and jvmMain\nval jvmAndroid = create(\"jvmAndroid\") {\n    dependsOn(commonMain.get())\n\n    dependencies {\n        api(libs.jackson.module.kotlin)  // JSON parsing - JVM only\n        api(libs.url.detector)            // URL extraction - JVM only\n        implementation(libs.okhttp)       // HTTP client - JVM only\n    }\n}\n\n// Both depend on jvmAndroid\njvmMain { dependsOn(jvmAndroid) }\nandroidMain { dependsOn(jvmAndroid) }\n```\n\n**Why Jackson in jvmAndroid, not commonMain?**\n- Jackson is JVM-specific library\n- Works on Android (runs on JVM)\n- Works on Desktop (runs on JVM)\n- Does NOT work on iOS (not JVM) or web (not JVM)\n\n**Web/wasm consideration:** For future web support, consider migrating from Jackson → kotlinx.serialization (see Target-Specific Guidance).\n\n## What to Abstract vs Keep Platform-Specific\n\nQuick decision guidelines based on codebase patterns:\n\n### Always Abstract\n- **Crypto** (Secp256k1, encryption, signing)\n- **Core protocol logic** (Nostr events, NIPs)\n- **Why:** Needed everywhere, platform security APIs vary\n\n### Often Abstract\n- **I/O operations** (file reading, caching)\n- **Logging** (platform logging systems differ)\n- **Serialization** (if using kotlinx.serialization)\n- **Why:** Commonly reused, platform implementations available\n\n### Sometimes Abstract\n- **Business logic:** YES - state machines, data processing\n- **ViewModels:** YES - state + business logic shareable (StateFlow/SharedFlow)\n- **Screen layouts:** NO - platform-native (Window vs Activity)\n- **Why:** ViewModels contain platform-agnostic state; Screens render differently per platform\n\n### Rarely Abstract\n- **Complex UI components** (composables with heavy platform dependencies)\n- **Why:** Platform paradigms can differ significantly\n\n### Never Abstract\n- **Navigation** (Activity vs Window fundamentally different)\n- **Permissions** (Android vs iOS APIs incompatible)\n- **Platform UX patterns**\n- **Why:** Too platform-specific, abstraction creates leaky APIs\n\n### Evidence from shared-ui-analysis.md\n\n| Component | Shared? | Rationale |\n|-----------|---------|-----------|\n| PubKeyFormatter, ZapFormatter | ✅ YES | Pure Kotlin, no platform APIs |\n| TimeAgoFormatter | ⚠️ ABSTRACTED | Needs StringProvider for localized strings |\n| ViewModels (state + logic) | ✅ YES | StateFlow/SharedFlow platform-agnostic, Compose Multiplatform lifecycle compatible |\n| Screen layouts (Scaffold, nav) | ❌ NO | Window vs Activity, sidebar vs bottom nav fundamentally different |\n| Image loading (Coil) | ⚠️ ABSTRACTED | Coil 3.x supports KMP, needs expect/actual wrapper |\n\n## expect/actual Mechanics\n\n**When to use:** Code needed by 2+ platforms, varies by platform.\n\n### Pattern Categories from Codebase\n\n**Objects (singletons):**\n```kotlin\n// 24 expect declarations found, common pattern:\nexpect object Secp256k1Instance { ... }\nexpect object Log { ... }\nexpect object LibSodiumInstance { ... }\n```\n\n**Classes (instantiable):**\n```kotlin\nexpect class AESCBC { ... }\nexpect class DigestInstance { ... }\n```\n\n**Functions (utilities):**\n```kotlin\nexpect fun platform(): String\nexpect fun currentTimeSeconds(): Long\n```\n\n**See** [references/expect-actual-catalog.md](references/expect-actual-catalog.md) for complete catalog with rationale.\n\n## Target-Specific Guidance\n\n### Android, JVM (Desktop), iOS - Current Primary Targets\n\n**Status:** Mature patterns, stable APIs\n\n**Android (androidMain):**\n- Uses Android framework (Activity, Context, etc.)\n- secp256k1-kmp-jni-android for crypto\n- AndroidX libraries\n\n**Desktop JVM (jvmMain):**\n- Uses Compose Desktop (Window, MenuBar, etc.)\n- secp256k1-kmp-jni-jvm for crypto\n- Pure JVM libraries\n\n**iOS (iosMain):**\n- Active development, framework configured\n- Architecture targets: iosX64Main, iosArm64Main, iosSimulatorArm64Main\n- Platform APIs via platform.posix, Security framework\n\n### Web, wasm - Future Targets\n\n**Status:** Not yet implemented, consider for future-proofing\n\n**Constraints to know:**\n- ❌ No platform.posix (file I/O different)\n- ❌ No JVM libraries (Jackson, OkHttp won't work)\n- ❌ Different async model (JS event loop vs threads)\n\n**Future-proofing tips:**\n1. Prefer pure Kotlin in commonMain\n2. Use kotlinx.* libraries:\n   - kotlinx.serialization instead of Jackson\n   - ktor instead of OkHttp (ktor supports web)\n   - kotlinx.datetime instead of custom date handling\n3. Avoid platform.posix for file operations\n4. Test abstractions work without JVM assumptions\n\n**Example migration path:**\n```kotlin\n// Current: jvmAndroid (JVM-only)\napi(libs.jackson.module.kotlin)\n\n// Future: commonMain (all platforms)\napi(libs.kotlinx.serialization.json)\n```\n\n## Integration: When to Invoke Other Skills\n\n### Invoke gradle-expert\n\nTrigger gradle-expert skill when encountering:\n- Dependency conflicts (e.g., secp256k1-android vs secp256k1-jvm version mismatch)\n- Build errors related to source sets\n- Version catalog issues (libs.versions.toml)\n- \"Duplicate class\" errors\n- Performance/build time issues\n\n**Example trigger:**\n```\nError: Duplicate class found: fr.acinq.secp256k1.Secp256k1\n```\n→ Invoke gradle-expert for dependency conflict resolution.\n\n### Flags to Raise\n\n**Platform code in commonMain:**\n```kotlin\n// ❌ INCORRECT - Android API in commonMain\nexpect fun getContext(): Context  // Context is Android-only!\n```\n→ Flag: \"Android API in commonMain won't compile on other platforms\"\n\n**Duplicated business logic:**\n```kotlin\n// ❌ INCORRECT - Same logic in both\n// androidMain/.../CryptoUtils.kt\nfun validateSignature(...) { ... }\n\n// jvmMain/.../CryptoUtils.kt\nfun validateSignature(...) { ... }  // Duplicated!\n```\n→ Flag: \"Business logic duplicated, should be in commonMain or expect/actual\"\n\n**Reinventing wheel - suggest KMP alternatives:**\n- Custom date/time → kotlinx.datetime\n- OkHttp → ktor (supports web)\n- Jackson → kotlinx.serialization\n- Custom UUID → kotlinx.uuid (when stable)\n\n## Common Pitfalls\n\n### 1. Over-Abstraction\n**Problem:** Creating expect/actual for UI components\n```kotlin\n// ❌ BAD\nexpect fun NavigationComponent(...)\n```\n**Why:** Navigation paradigms too different (Activity vs Window)\n**Fix:** Keep platform-specific, accept duplication\n\n### 2. Under-Sharing\n**Problem:** Duplicating business logic across platforms\n```kotlin\n// ❌ BAD - duplicated in androidMain and jvmMain\nfun parseNostrEvent(json: String): Event { ... }\n```\n**Why:** Bug fixes need to be applied twice, tests duplicated\n**Fix:** Move to commonMain (pure Kotlin) or create expect/actual\n\n### 3. Leaky Abstractions\n**Problem:** Platform code in commonMain\n```kotlin\n// commonMain - ❌ BAD\nimport android.content.Context  // Won't compile on iOS!\n```\n**Fix:** Use expect/actual or dependency injection\n\n### 4. Premature Abstraction\n**Problem:** Creating expect/actual before second platform needs it\n```kotlin\n// ❌ BAD - only used on Android currently\nexpect fun showNotification(...)\n```\n**Why:** Wrong abstraction boundaries, wasted effort\n**Fix:** Wait until iOS actually needs it, then abstract\n\n### 5. Wrong Source Set\n**Problem:** JVM libraries in commonMain\n```kotlin\n// commonMain - ❌ BAD\nimport com.fasterxml.jackson.databind.ObjectMapper\n```\n**Why:** Jackson won't compile on iOS/web\n**Fix:** Move to jvmAndroid or migrate to kotlinx.serialization\n\n## Quick Reference\n\n| Code Type | Recommended Location | Reason |\n|-----------|---------------------|--------|\n| Pure Kotlin business logic | commonMain | Works everywhere |\n| Nostr protocol, NIPs | commonMain | Core logic, no platform APIs |\n| JVM libs (Jackson, OkHttp) | jvmAndroid | Android + Desktop only |\n| Crypto (varies by platform) | expect in commonMain, actual in platforms | Different security APIs per platform |\n| I/O, logging | expect in commonMain, actual in platforms | Platform implementations differ |\n| State (business logic) | commonMain or commons/jvmAndroid | Reusable StateFlow patterns |\n| **ViewModels** | **commons/commonMain/viewmodels/** | **StateFlow/SharedFlow + logic shareable, Compose MP lifecycle compatible** |\n| UI formatters (pure) | commons/commonMain | Reusable, no dependencies |\n| UI components (simple) | commons/commonMain | Cards, buttons, dialogs |\n| **Screen layouts** | **Platform-specific** | **Window vs Activity, sidebar vs bottom nav** |\n| Navigation | Platform-specific only | Activity vs Window too different |\n| Permissions | Platform-specific only | APIs incompatible |\n| Platform UX (menus, etc.) | Platform-specific only | Native feel required |\n\n## See Also\n\n- [references/abstraction-examples.md](references/abstraction-examples.md) - Good/bad abstraction examples with rationale\n- [references/source-set-hierarchy.md](references/source-set-hierarchy.md) - Visual hierarchy with Amethyst examples\n- [references/expect-actual-catalog.md](references/expect-actual-catalog.md) - All 24 expect/actual pairs with \"why abstracted\"\n- [references/target-compatibility.md](references/target-compatibility.md) - Platform constraints and future-proofing\n\n## Scripts\n\n- `scripts/validate-kmp-structure.sh` - Detect incorrect placements, validate source sets\n- `scripts/suggest-kmp-dependency.sh` - Suggest KMP library alternatives (ktor, kotlinx.serialization, etc.)\n",".claude/skills/compose-ui/SKILL.md":"---\nname: compose-ui\ndescription: Best practices for building UI with Jetpack Compose, focusing on state hoisting, detailed performance optimizations, and theming. Use this when writing or refactoring Composable functions.\n---\n\n# Jetpack Compose Best Practices\n\n## Instructions\n\nFollow these guidelines to create performant, reusable, and testable Composables.\n\n### 1. State Hoisting (Unidirectional Data Flow)\nMake Composables **stateless** whenever possible by moving state to the caller.\n\n*   **Pattern**: Function signature should usually look like:\n    ```kotlin\n    @Composable\n    fun MyComponent(\n        value: String,              // State flows down\n        onValueChange: (String) -> Unit, // Events flow up\n        modifier: Modifier = Modifier // Standard modifier parameter\n    )\n    ```\n*   **Benefit**: Decouples the UI from simple state storage, making it easier to preview and test.\n*   **ViewModel Integration**: The screen-level Composable retrieves state from the ViewModel (`viewModel.uiState.collectAsStateWithLifecycle()`) and passes it down.\n\n### 2. Modifiers\n*   **Default Parameter**: Always provide a `modifier: Modifier = Modifier` as the first optional parameter.\n*   **Application**: Apply this `modifier` to the *root* layout element of your Composable.\n*   **Ordering matters**: `padding().clickable()` is different from `clickable().padding()`. Generally apply layout-affecting modifiers (like padding) *after* click listeners if you want the padding to be clickable.\n\n### 3. Performance Optimization\n*   **`remember`**: Use `remember { ... }` to cache expensive calculations across recompositions.\n*   **`derivedStateOf`**: Use `derivedStateOf { ... }` when a state changes frequently (like scroll position) but the UI only needs to react to a threshold or summary (e.g., show \"Jump to Top\" button). This prevents unnecessary recompositions.\n    ```kotlin\n    val showButton by remember {\n        derivedStateOf { listState.firstVisibleItemIndex > 0 }\n    }\n    ```\n*   **Lambda Stability**: Prefer method references (e.g., `viewModel::onEvent`) or remembered lambdas to prevent unstable types from triggering recomposition of children.\n\n### 4. Theming and Resources\n*   Use `MaterialTheme.colorScheme` and `MaterialTheme.typography` instead of hardcoded colors or text styles.\n*   Organize simple UI components into specific files (e.g., `DesignSystem.kt` or `Components.kt`) if they are shared across features.\n\n### 5. Previews\n*   Create a private preview function for every public Composable.\n*   Use `@Preview(showBackground = true)` and include Light/Dark mode previews if applicable.\n*   Pass dummy data (static) to the stateless Composable for the preview.\n",".claude/skills/gradle-build-performance/SKILL.md":"---\nname: gradle-build-performance\ndescription: Debug and optimize Android/Gradle build performance. Use when builds are slow, investigating CI/CD performance, analyzing build scans, or identifying compilation bottlenecks.\n---\n\n# Gradle Build Performance\n\n## When to Use\n\n- Build times are slow (clean or incremental)\n- Investigating build performance regressions\n- Analyzing Gradle Build Scans\n- Identifying configuration vs execution bottlenecks\n- Optimizing CI/CD build times\n- Enabling Gradle Configuration Cache\n- Reducing unnecessary recompilation\n- Debugging kapt/KSP annotation processing\n\n## Example Prompts\n\n- \"My builds are slow, how can I speed them up?\"\n- \"How do I analyze a Gradle build scan?\"\n- \"Why is configuration taking so long?\"\n- \"Why does my project always recompile everything?\"\n- \"How do I enable configuration cache?\"\n- \"Why is kapt so slow?\"\n\n---\n\n## Workflow\n\n1. **Measure Baseline** — Clean build + incremental build times\n2. **Generate Build Scan** — `./gradlew assembleDebug --scan`\n3. **Identify Phase** — Configuration? Execution? Dependency resolution?\n4. **Apply ONE optimization** — Don't batch changes\n5. **Measure Improvement** — Compare against baseline\n6. **Verify in Build Scan** — Visual confirmation\n\n---\n\n## Quick Diagnostics\n\n### Generate Build Scan\n\n```bash\n./gradlew assembleDebug --scan\n```\n\n### Profile Build Locally\n\n```bash\n./gradlew assembleDebug --profile\n# Opens report in build/reports/profile/\n```\n\n### Build Timing Summary\n\n```bash\n./gradlew assembleDebug --info | grep -E \"^\\:.*\"\n# Or view in Android Studio: Build > Analyze APK Build\n```\n\n---\n\n## Build Phases\n\n| Phase | What Happens | Common Issues |\n|-------|--------------|---------------|\n| **Initialization** | `settings.gradle.kts` evaluated | Too many `include()` statements |\n| **Configuration** | All `build.gradle.kts` files evaluated | Expensive plugins, eager task creation |\n| **Execution** | Tasks run based on inputs/outputs | Cache misses, non-incremental tasks |\n\n### Identify the Bottleneck\n\n```\nBuild scan → Performance → Build timeline\n```\n\n- **Long configuration phase**: Focus on plugin and buildscript optimization\n- **Long execution phase**: Focus on task caching and parallelization\n- **Dependency resolution slow**: Focus on repository configuration\n\n---\n\n## 12 Optimization Patterns\n\n### 1. Enable Configuration Cache\n\nCaches configuration phase across builds (AGP 8.0+):\n\n```properties\n# gradle.properties\norg.gradle.configuration-cache=true\norg.gradle.configuration-cache.problems=warn\n```\n\n### 2. Enable Build Cache\n\nReuses task outputs across builds and machines:\n\n```properties\n# gradle.properties\norg.gradle.caching=true\n```\n\n### 3. Enable Parallel Execution\n\nBuild independent modules simultaneously:\n\n```properties\n# gradle.properties\norg.gradle.parallel=true\n```\n\n### 4. Increase JVM Heap\n\nAllocate more memory for large projects:\n\n```properties\n# gradle.properties\norg.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC\n```\n\n### 5. Use Non-Transitive R Classes\n\nReduces R class size and compilation (AGP 8.0+ default):\n\n```properties\n# gradle.properties\nandroid.nonTransitiveRClass=true\n```\n\n### 6. Migrate kapt to KSP\n\nKSP is 2x faster than kapt for Kotlin:\n\n```kotlin\n// Before (slow)\nkapt(\"com.google.dagger:hilt-compiler:2.51.1\")\n\n// After (fast)\nksp(\"com.google.dagger:hilt-compiler:2.51.1\")\n```\n\n### 7. Avoid Dynamic Dependencies\n\nPin dependency versions:\n\n```kotlin\n// BAD: Forces resolution every build\nimplementation(\"com.example:lib:+\")\nimplementation(\"com.example:lib:1.0.+\")\n\n// GOOD: Fixed version\nimplementation(\"com.example:lib:1.2.3\")\n```\n\n### 8. Optimize Repository Order\n\nPut most-used repositories first:\n\n```kotlin\n// settings.gradle.kts\ndependencyResolutionManagement {\n    repositories {\n        google()      // First: Android dependencies\n        mavenCentral() // Second: Most libraries\n        // Third-party repos last\n    }\n}\n```\n\n### 9. Use includeBuild for Local Modules\n\nComposite builds are faster than `project()` for large monorepos:\n\n```kotlin\n// settings.gradle.kts\nincludeBuild(\"shared-library\") {\n    dependencySubstitution {\n        substitute(module(\"com.example:shared\")).using(project(\":\"))\n    }\n}\n```\n\n### 10. Enable Incremental Annotation Processing\n\n```properties\n# gradle.properties\nkapt.incremental.apt=true\nkapt.use.worker.api=true\n```\n\n### 11. Avoid Configuration-Time I/O\n\nDon't read files or make network calls during configuration:\n\n```kotlin\n// BAD: Runs during configuration\nval version = file(\"version.txt\").readText()\n\n// GOOD: Defer to execution\nval version = providers.fileContents(file(\"version.txt\")).asText\n```\n\n### 12. Use Lazy Task Configuration\n\nAvoid `create()`, use `register()`:\n\n```kotlin\n// BAD: Eagerly configured\ntasks.create(\"myTask\") { ... }\n\n// GOOD: Lazily configured\ntasks.register(\"myTask\") { ... }\n```\n\n---\n\n## Common Bottleneck Analysis\n\n### Slow Configuration Phase\n\n**Symptoms**: Build scan shows long \"Configuring build\" time\n\n**Causes & Fixes**:\n| Cause | Fix |\n|-------|-----|\n| Eager task creation | Use `tasks.register()` instead of `tasks.create()` |\n| buildSrc with many dependencies | Migrate to Convention Plugins with `includeBuild` |\n| File I/O in build scripts | Use `providers.fileContents()` |\n| Network calls in plugins | Cache results or use offline mode |\n\n### Slow Compilation\n\n**Symptoms**: `:app:compileDebugKotlin` takes too long\n\n**Causes & Fixes**:\n| Cause | Fix |\n|-------|-----|\n| Non-incremental changes | Avoid `build.gradle.kts` changes that invalidate cache |\n| Large modules | Break into smaller feature modules |\n| Excessive kapt usage | Migrate to KSP |\n| Kotlin compiler memory | Increase `kotlin.daemon.jvmargs` |\n\n### Cache Misses\n\n**Symptoms**: Tasks always rerun despite no changes\n\n**Causes & Fixes**:\n| Cause | Fix |\n|-------|-----|\n| Unstable task inputs | Use `@PathSensitive`, `@NormalizeLineEndings` |\n| Absolute paths in outputs | Use relative paths |\n| Missing `@CacheableTask` | Add annotation to custom tasks |\n| Different JDK versions | Standardize JDK across environments |\n\n---\n\n## CI/CD Optimizations\n\n### Remote Build Cache\n\n```kotlin\n// settings.gradle.kts\nbuildCache {\n    local { isEnabled = true }\n    remote<HttpBuildCache> {\n        url = uri(\"https://cache.example.com/\")\n        isPush = System.getenv(\"CI\") == \"true\"\n        credentials {\n            username = System.getenv(\"CACHE_USER\")\n            password = System.getenv(\"CACHE_PASS\")\n        }\n    }\n}\n```\n\n### Gradle Enterprise / Develocity\n\nFor advanced build analytics:\n\n```kotlin\n// settings.gradle.kts\nplugins {\n    id(\"com.gradle.develocity\") version \"3.17\"\n}\n\ndevelocity {\n    buildScan {\n        termsOfUseUrl.set(\"https://gradle.com/help/legal-terms-of-use\")\n        termsOfUseAgree.set(\"yes\")\n        publishing.onlyIf { System.getenv(\"CI\") != null }\n    }\n}\n```\n\n### Skip Unnecessary Tasks in CI\n\n```bash\n# Skip tests for UI-only changes\n./gradlew assembleDebug -x test -x lint\n\n# Only run affected module tests\n./gradlew :feature:login:test\n```\n\n---\n\n## Android Studio Settings\n\n### File → Settings → Build → Gradle\n\n- **Gradle JDK**: Match your project's JDK\n- **Build and run using**: Gradle (not IntelliJ)\n- **Run tests using**: Gradle\n\n### File → Settings → Build → Compiler\n\n- **Compile independent modules in parallel**: ✅ Enabled\n- **Configure on demand**: ❌ Disabled (deprecated)\n\n---\n\n## Verification Checklist\n\nAfter optimizations, verify:\n\n- [ ] Configuration cache enabled and working\n- [ ] Build cache hit rate > 80% (check build scan)\n- [ ] No dynamic dependency versions\n- [ ] KSP used instead of kapt where possible\n- [ ] Parallel execution enabled\n- [ ] JVM memory tuned appropriately\n- [ ] CI remote cache configured\n- [ ] No configuration-time I/O\n\n---\n\n## References\n\n- [Optimize Build Speed](https://developer.android.com/build/optimize-your-build)\n- [Gradle Configuration Cache](https://docs.gradle.org/current/userguide/configuration_cache.html)\n- [Gradle Build Cache](https://docs.gradle.org/current/userguide/build_cache.html)\n- [Migrate from kapt to KSP](https://developer.android.com/build/migrate-to-ksp)\n- [Gradle Build Scans](https://scans.gradle.com/)\n",".claude/skills/compose-performance-audit/SKILL.md":"---\nname: compose-performance-audit\ndescription: Audit and improve Jetpack Compose runtime performance from code review and architecture. Use when asked to diagnose slow rendering, janky scrolling, excessive recompositions, or performance issues in Compose UI.\n---\n\n# Compose Performance Audit\n\n## Overview\n\nAudit Jetpack Compose view performance end-to-end, from instrumentation and baselining to root-cause analysis and concrete remediation steps.\n\n## Workflow Decision Tree\n\n- If the user provides code, start with \"Code-First Review.\"\n- If the user only describes symptoms, ask for minimal code/context, then do \"Code-First Review.\"\n- If code review is inconclusive, go to \"Guide the User to Profile\" and ask for Layout Inspector output or Perfetto traces.\n\n## 1. Code-First Review\n\nCollect:\n- Target Composable code.\n- Data flow: state, remember, derived state, ViewModel connections.\n- Symptoms and reproduction steps.\n\nFocus on:\n- **Recomposition storms** from unstable parameters or broad state changes.\n- **Unstable keys** in `LazyColumn`/`LazyRow` (`key` churn, missing keys).\n- **Heavy work in composition** (formatting, sorting, filtering, object allocation).\n- **Unnecessary recompositions** (missing `remember`, unstable classes, lambdas).\n- **Large images** without proper sizing or async loading.\n- **Layout thrash** (deep nesting, intrinsic measurements, `SubcomposeLayout` misuse).\n\nProvide:\n- Likely root causes with code references.\n- Suggested fixes and refactors.\n- If needed, a minimal repro or instrumentation suggestion.\n\n## 2. Guide the User to Profile\n\nExplain how to collect data:\n- Use **Layout Inspector** in Android Studio to see recomposition counts.\n- Enable **Recomposition Highlights** in Compose tooling.\n- Use **Perfetto** or **System Trace** for frame timing analysis.\n- Check **Macrobenchmark** results for startup/scroll metrics.\n\nAsk for:\n- Layout Inspector screenshot showing recomposition counts.\n- Perfetto trace or System Trace export.\n- Device/OS/build configuration (debug vs release).\n\n> **Important**: Ensure profiling is done on a **release build** with R8 enabled. Debug builds have significant overhead.\n\n## 3. Analyze and Diagnose\n\nPrioritize likely Compose culprits:\n- **Recomposition storms** from unstable parameters or broad state changes.\n- **Unstable keys** in lazy lists (`key` churn, index-based keys).\n- **Heavy work in composition** (formatting, sorting, object allocation).\n- **Missing `remember`** causing recreations on every recomposition.\n- **Large images** without `Modifier.size()` constraints.\n- **Unnecessary state reads** in wrong composition phases.\n\nSummarize findings with evidence from traces/Layout Inspector.\n\n## 4. Remediate\n\nApply targeted fixes:\n- **Stabilize parameters**: Use `@Stable` or `@Immutable` annotations on data classes.\n- **Stabilize keys**: Use stable, unique IDs for `LazyColumn`/`LazyRow` items.\n- **Defer state reads**: Use `derivedStateOf`, lambda-based modifiers, or `Modifier.drawBehind`.\n- **Remember expensive computations**: Wrap in `remember { }` or `remember(key) { }`.\n- **Skip recomposition**: Extract stable composables, use `key()` to control identity.\n- **Async image loading**: Use Coil/Glide with proper sizing constraints.\n- **Reduce layout complexity**: Flatten hierarchies, avoid deep nesting.\n\n## Common Code Smells (and Fixes)\n\n### Unstable lambda captures\n\n```kotlin\n// BAD: New lambda instance every recomposition\nButton(onClick = { viewModel.doSomething(item) }) { ... }\n\n// GOOD: Use remember or method reference\nval onClick = remember(item) { { viewModel.doSomething(item) } }\nButton(onClick = onClick) { ... }\n```\n\n### Expensive work in composition\n\n```kotlin\n// BAD: Sorting on every recomposition\n@Composable\nfun ItemList(items: List<Item>) {\n    val sorted = items.sortedBy { it.name } // Runs every recomposition\n    LazyColumn { items(sorted) { ... } }\n}\n\n// GOOD: Use remember with key\n@Composable\nfun ItemList(items: List<Item>) {\n    val sorted = remember(items) { items.sortedBy { it.name } }\n    LazyColumn { items(sorted) { ... } }\n}\n```\n\n### Missing keys in LazyColumn\n\n```kotlin\n// BAD: Index-based identity (causes recomposition on list changes)\nLazyColumn {\n    items(items) { item -> ItemRow(item) }\n}\n\n// GOOD: Stable key-based identity\nLazyColumn {\n    items(items, key = { it.id }) { item -> ItemRow(item) }\n}\n```\n\n### Unstable data classes\n\n```kotlin\n// BAD: Unstable (contains List, which is not stable)\ndata class UiState(\n    val items: List<Item>,\n    val isLoading: Boolean\n)\n\n// GOOD: Mark as Immutable if truly immutable\n@Immutable\ndata class UiState(\n    val items: ImmutableList<Item>, // kotlinx.collections.immutable\n    val isLoading: Boolean\n)\n```\n\n### Reading state too early\n\n```kotlin\n// BAD: State read during composition (recomposes whole tree)\n@Composable\nfun AnimatedBox(scrollState: ScrollState) {\n    val offset = scrollState.value // Recomposes on every scroll\n    Box(modifier = Modifier.offset(y = offset.dp)) { ... }\n}\n\n// GOOD: Defer state read to layout/draw phase\n@Composable\nfun AnimatedBox(scrollState: ScrollState) {\n    Box(modifier = Modifier.offset {\n        IntOffset(0, scrollState.value) // Read in layout phase\n    }) { ... }\n}\n```\n\n### Object allocation in composition\n\n```kotlin\n// BAD: Creates new Modifier chain every recomposition\nBox(modifier = Modifier.padding(16.dp).background(Color.Red))\n\n// GOOD for dynamic modifiers: Remember the modifier\nval modifier = remember { Modifier.padding(16.dp).background(Color.Red) }\nBox(modifier = modifier)\n```\n\n## Stability Checklist\n\n| Type | Stable by Default? | Fix |\n|------|-------------------|-----|\n| Primitives (`Int`, `String`, `Boolean`) | Yes | N/A |\n| `data class` with stable fields | Yes* | Ensure all fields are stable |\n| `List`, `Map`, `Set` | **No** | Use `ImmutableList` from kotlinx |\n| Classes with `var` properties | **No** | Use `@Stable` if externally stable |\n| Lambdas | **No** | Use `remember { }` |\n\n## 5. Verify\n\nAsk the user to:\n- Re-run Layout Inspector and compare recomposition counts.\n- Run Macrobenchmark and compare frame timing.\n- Test on a real device with release build.\n\nSummarize the delta (recomposition count, frame drops, jank) if provided.\n\n## Outputs\n\nProvide:\n- A short metrics table (before/after if available).\n- Top issues (ordered by impact).\n- Proposed fixes with estimated effort.\n\n## References\n\n- [Jetpack Compose Performance](https://developer.android.com/develop/ui/compose/performance)\n- [Compose Stability Explained](https://developer.android.com/develop/ui/compose/performance/stability)\n- [Debugging Recomposition](https://developer.android.com/develop/ui/compose/tooling/layout-inspector)\n- [Macrobenchmark](https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview)\n",".claude/skills/mobile-android-design/SKILL.md":"---\nname: mobile-android-design\ndescription: Master Material Design 3 and Jetpack Compose patterns for building native Android apps. Use when designing Android interfaces, implementing Compose UI, or following Google's Material Design guidelines.\n---\n\n# Android Mobile Design\n\nMaster Material Design 3 (Material You) and Jetpack Compose to build modern, adaptive Android applications that integrate seamlessly with the Android ecosystem.\n\n## When to Use This Skill\n\n- Designing Android app interfaces following Material Design 3\n- Building Jetpack Compose UI and layouts\n- Implementing Android navigation patterns (Navigation Compose)\n- Creating adaptive layouts for phones, tablets, and foldables\n- Using Material 3 theming with dynamic colors\n- Building accessible Android interfaces\n- Implementing Android-specific gestures and interactions\n- Designing for different screen configurations\n\n## Core Concepts\n\n### 1. Material Design 3 Principles\n\n**Personalization**: Dynamic color adapts UI to user's wallpaper\n**Accessibility**: Tonal palettes ensure sufficient color contrast\n**Large Screens**: Responsive layouts for tablets and foldables\n\n**Material Components:**\n\n- Cards, Buttons, FABs, Chips\n- Navigation (rail, drawer, bottom nav)\n- Text fields, Dialogs, Sheets\n- Lists, Menus, Progress indicators\n\n### 2. Jetpack Compose Layout System\n\n**Column and Row:**\n\n```kotlin\n// Vertical arrangement with alignment\nColumn(\n    modifier = Modifier.padding(16.dp),\n    verticalArrangement = Arrangement.spacedBy(12.dp),\n    horizontalAlignment = Alignment.Start\n) {\n    Text(\n        text = \"Title\",\n        style = MaterialTheme.typography.headlineSmall\n    )\n    Text(\n        text = \"Subtitle\",\n        style = MaterialTheme.typography.bodyMedium,\n        color = MaterialTheme.colorScheme.onSurfaceVariant\n    )\n}\n\n// Horizontal arrangement with weight\nRow(\n    modifier = Modifier.fillMaxWidth(),\n    horizontalArrangement = Arrangement.SpaceBetween,\n    verticalAlignment = Alignment.CenterVertically\n) {\n    Icon(Icons.Default.Star, contentDescription = null)\n    Text(\"Featured\")\n    Spacer(modifier = Modifier.weight(1f))\n    TextButton(onClick = {}) {\n        Text(\"View All\")\n    }\n}\n```\n\n**Lazy Lists and Grids:**\n\n```kotlin\n// Lazy column with sticky headers\nLazyColumn {\n    items.groupBy { it.category }.forEach { (category, categoryItems) ->\n        stickyHeader {\n            Text(\n                text = category,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.surface)\n                    .padding(16.dp),\n                style = MaterialTheme.typography.titleMedium\n            )\n        }\n        items(categoryItems) { item ->\n            ItemRow(item = item)\n        }\n    }\n}\n\n// Adaptive grid\nLazyVerticalGrid(\n    columns = GridCells.Adaptive(minSize = 150.dp),\n    contentPadding = PaddingValues(16.dp),\n    horizontalArrangement = Arrangement.spacedBy(12.dp),\n    verticalArrangement = Arrangement.spacedBy(12.dp)\n) {\n    items(items) { item ->\n        ItemCard(item = item)\n    }\n}\n```\n\n### 3. Navigation Patterns\n\n**Bottom Navigation:**\n\n```kotlin\n@Composable\nfun MainScreen() {\n    val navController = rememberNavController()\n\n    Scaffold(\n        bottomBar = {\n            NavigationBar {\n                val navBackStackEntry by navController.currentBackStackEntryAsState()\n                val currentDestination = navBackStackEntry?.destination\n\n                NavigationDestination.entries.forEach { destination ->\n                    NavigationBarItem(\n                        icon = { Icon(destination.icon, contentDescription = null) },\n                        label = { Text(destination.label) },\n                        selected = currentDestination?.hierarchy?.any {\n                            it.route == destination.route\n                        } == true,\n                        onClick = {\n                            navController.navigate(destination.route) {\n                                popUpTo(navController.graph.findStartDestination().id) {\n                                    saveState = true\n                                }\n                                launchSingleTop = true\n                                restoreState = true\n                            }\n                        }\n                    )\n                }\n            }\n        }\n    ) { innerPadding ->\n        NavHost(\n            navController = navController,\n            startDestination = NavigationDestination.Home.route,\n            modifier = Modifier.padding(innerPadding)\n        ) {\n            composable(NavigationDestination.Home.route) { HomeScreen() }\n            composable(NavigationDestination.Search.route) { SearchScreen() }\n            composable(NavigationDestination.Profile.route) { ProfileScreen() }\n        }\n    }\n}\n```\n\n**Navigation Drawer:**\n\n```kotlin\n@Composable\nfun DrawerNavigation() {\n    val drawerState = rememberDrawerState(DrawerValue.Closed)\n    val scope = rememberCoroutineScope()\n\n    ModalNavigationDrawer(\n        drawerState = drawerState,\n        drawerContent = {\n            ModalDrawerSheet {\n                Spacer(Modifier.height(12.dp))\n                Text(\n                    \"App Name\",\n                    modifier = Modifier.padding(16.dp),\n                    style = MaterialTheme.typography.titleLarge\n                )\n                HorizontalDivider()\n\n                NavigationDrawerItem(\n                    icon = { Icon(Icons.Default.Home, null) },\n                    label = { Text(\"Home\") },\n                    selected = true,\n                    onClick = { scope.launch { drawerState.close() } }\n                )\n                NavigationDrawerItem(\n                    icon = { Icon(Icons.Default.Settings, null) },\n                    label = { Text(\"Settings\") },\n                    selected = false,\n                    onClick = { }\n                )\n            }\n        }\n    ) {\n        Scaffold(\n            topBar = {\n                TopAppBar(\n                    title = { Text(\"Home\") },\n                    navigationIcon = {\n                        IconButton(onClick = { scope.launch { drawerState.open() } }) {\n                            Icon(Icons.Default.Menu, contentDescription = \"Menu\")\n                        }\n                    }\n                )\n            }\n        ) { innerPadding ->\n            Content(modifier = Modifier.padding(innerPadding))\n        }\n    }\n}\n```\n\n### 4. Material 3 Theming\n\n**Color Scheme:**\n\n```kotlin\n// Dynamic color (Android 12+)\nval dynamicColorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n    val context = LocalContext.current\n    if (darkTheme) dynamicDarkColorScheme(context)\n    else dynamicLightColorScheme(context)\n} else {\n    if (darkTheme) DarkColorScheme else LightColorScheme\n}\n\n// Custom color scheme\nprivate val LightColorScheme = lightColorScheme(\n    primary = Color(0xFF6750A4),\n    onPrimary = Color.White,\n    primaryContainer = Color(0xFFEADDFF),\n    onPrimaryContainer = Color(0xFF21005D),\n    secondary = Color(0xFF625B71),\n    onSecondary = Color.White,\n    tertiary = Color(0xFF7D5260),\n    onTertiary = Color.White,\n    surface = Color(0xFFFFFBFE),\n    onSurface = Color(0xFF1C1B1F),\n)\n```\n\n**Typography:**\n\n```kotlin\nval AppTypography = Typography(\n    displayLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 57.sp,\n        lineHeight = 64.sp\n    ),\n    headlineMedium = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 28.sp,\n        lineHeight = 36.sp\n    ),\n    titleLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 22.sp,\n        lineHeight = 28.sp\n    ),\n    bodyLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 16.sp,\n        lineHeight = 24.sp\n    ),\n    labelMedium = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Medium,\n        fontSize = 12.sp,\n        lineHeight = 16.sp\n    )\n)\n```\n\n### 5. Component Examples\n\n**Cards:**\n\n```kotlin\n@Composable\nfun FeatureCard(\n    title: String,\n    description: String,\n    imageUrl: String,\n    onClick: () -> Unit\n) {\n    Card(\n        onClick = onClick,\n        modifier = Modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(16.dp),\n        colors = CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surfaceVariant\n        )\n    ) {\n        Column {\n            AsyncImage(\n                model = imageUrl,\n                contentDescription = null,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .height(180.dp),\n                contentScale = ContentScale.Crop\n            )\n            Column(modifier = Modifier.padding(16.dp)) {\n                Text(\n                    text = title,\n                    style = MaterialTheme.typography.titleMedium\n                )\n                Spacer(modifier = Modifier.height(8.dp))\n                Text(\n                    text = description,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            }\n        }\n    }\n}\n```\n\n**Buttons:**\n\n```kotlin\n// Filled button (primary action)\nButton(onClick = { }) {\n    Text(\"Continue\")\n}\n\n// Filled tonal button (secondary action)\nFilledTonalButton(onClick = { }) {\n    Icon(Icons.Default.Add, null)\n    Spacer(Modifier.width(8.dp))\n    Text(\"Add Item\")\n}\n\n// Outlined button\nOutlinedButton(onClick = { }) {\n    Text(\"Cancel\")\n}\n\n// Text button\nTextButton(onClick = { }) {\n    Text(\"Learn More\")\n}\n\n// FAB\nFloatingActionButton(\n    onClick = { },\n    containerColor = MaterialTheme.colorScheme.primaryContainer,\n    contentColor = MaterialTheme.colorScheme.onPrimaryContainer\n) {\n    Icon(Icons.Default.Add, contentDescription = \"Add\")\n}\n```\n\n## Quick Start Component\n\n```kotlin\n@Composable\nfun ItemListCard(\n    item: Item,\n    onItemClick: () -> Unit,\n    modifier: Modifier = Modifier\n) {\n    Card(\n        onClick = onItemClick,\n        modifier = modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(12.dp)\n    ) {\n        Row(\n            modifier = Modifier\n                .padding(16.dp)\n                .fillMaxWidth(),\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            Box(\n                modifier = Modifier\n                    .size(48.dp)\n                    .clip(CircleShape)\n                    .background(MaterialTheme.colorScheme.primaryContainer),\n                contentAlignment = Alignment.Center\n            ) {\n                Icon(\n                    imageVector = Icons.Default.Star,\n                    contentDescription = null,\n                    tint = MaterialTheme.colorScheme.onPrimaryContainer\n                )\n            }\n\n            Spacer(modifier = Modifier.width(16.dp))\n\n            Column(modifier = Modifier.weight(1f)) {\n                Text(\n                    text = item.title,\n                    style = MaterialTheme.typography.titleMedium\n                )\n                Text(\n                    text = item.subtitle,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            }\n\n            Icon(\n                imageVector = Icons.Default.ChevronRight,\n                contentDescription = null,\n                tint = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n    }\n}\n```\n\n## Best Practices\n\n1. **Use Material Theme**: Access colors via `MaterialTheme.colorScheme` for automatic dark mode support\n2. **Support Dynamic Color**: Enable dynamic color on Android 12+ for personalization\n3. **Adaptive Layouts**: Use `WindowSizeClass` for responsive designs\n4. **Content Descriptions**: Add `contentDescription` to all interactive elements\n5. **Touch Targets**: Minimum 48dp touch targets for accessibility\n6. **State Hoisting**: Hoist state to make components reusable and testable\n7. **Remember Properly**: Use `remember` and `rememberSaveable` appropriately\n8. **Preview Annotations**: Add `@Preview` with different configurations\n\n## Common Issues\n\n- **Recomposition Issues**: Avoid passing unstable lambdas; use `remember`\n- **State Loss**: Use `rememberSaveable` for configuration changes\n- **Performance**: Use `LazyColumn` instead of `Column` for long lists\n- **Theme Leaks**: Ensure `MaterialTheme` wraps all composables\n- **Navigation Crashes**: Handle back press and deep links properly\n- **Memory Leaks**: Cancel coroutines in `DisposableEffect`\n\n## Resources\n\n- [Material Design 3](https://m3.material.io/)\n- [Jetpack Compose Documentation](https://developer.android.com/jetpack/compose)\n- [Compose Samples](https://github.com/android/compose-samples)\n- [Material 3 Compose](https://developer.android.com/jetpack/compose/designsystems/material3)\n",".claude/skills/find-skills/SKILL.md":"---\nname: find-skills\ndescription: Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.\n---\n\n# Find Skills\n\nThis skill helps you discover and install skills from the open agent skills ecosystem.\n\n## When to Use This Skill\n\nUse this skill when the user:\n\n- Asks \"how do I do X\" where X might be a common task with an existing skill\n- Says \"find a skill for X\" or \"is there a skill for X\"\n- Asks \"can you do X\" where X is a specialized capability\n- Expresses interest in extending agent capabilities\n- Wants to search for tools, templates, or workflows\n- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)\n\n## What is the Skills CLI?\n\nThe Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.\n\n**Key commands:**\n\n- `npx skills find [query]` - Search for skills interactively or by keyword\n- `npx skills add <package>` - Install a skill from GitHub or other sources\n- `npx skills check` - Check for skill updates\n- `npx skills update` - Update all installed skills\n\n**Browse skills at:** https://skills.sh/\n\n## How to Help Users Find Skills\n\n### Step 1: Understand What They Need\n\nWhen a user asks for help with something, identify:\n\n1. The domain (e.g., React, testing, design, deployment)\n2. The specific task (e.g., writing tests, creating animations, reviewing PRs)\n3. Whether this is a common enough task that a skill likely exists\n\n### Step 2: Search for Skills\n\nRun the find command with a relevant query:\n\n```bash\nnpx skills find [query]\n```\n\nFor example:\n\n- User asks \"how do I make my React app faster?\" → `npx skills find react performance`\n- User asks \"can you help me with PR reviews?\" → `npx skills find pr review`\n- User asks \"I need to create a changelog\" → `npx skills find changelog`\n\nThe command will return results like:\n\n```\nInstall with npx skills add <owner/repo@skill>\n\nvercel-labs/agent-skills@vercel-react-best-practices\n└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 3: Present Options to the User\n\nWhen you find relevant skills, present them to the user with:\n\n1. The skill name and what it does\n2. The install command they can run\n3. A link to learn more at skills.sh\n\nExample response:\n\n```\nI found a skill that might help! The \"vercel-react-best-practices\" skill provides\nReact and Next.js performance optimization guidelines from Vercel Engineering.\n\nTo install it:\nnpx skills add vercel-labs/agent-skills@vercel-react-best-practices\n\nLearn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 4: Offer to Install\n\nIf the user wants to proceed, you can install the skill for them:\n\n```bash\nnpx skills add <owner/repo@skill> -g -y\n```\n\nThe `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.\n\n## Common Skill Categories\n\nWhen searching, consider these common categories:\n\n| Category        | Example Queries                          |\n| --------------- | ---------------------------------------- |\n| Web Development | react, nextjs, typescript, css, tailwind |\n| Testing         | testing, jest, playwright, e2e           |\n| DevOps          | deploy, docker, kubernetes, ci-cd        |\n| Documentation   | docs, readme, changelog, api-docs        |\n| Code Quality    | review, lint, refactor, best-practices   |\n| Design          | ui, ux, design-system, accessibility     |\n| Productivity    | workflow, automation, git                |\n\n## Tips for Effective Searches\n\n1. **Use specific keywords**: \"react testing\" is better than just \"testing\"\n2. **Try alternative terms**: If \"deploy\" doesn't work, try \"deployment\" or \"ci-cd\"\n3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`\n\n## When No Skills Are Found\n\nIf no relevant skills exist:\n\n1. Acknowledge that no existing skill was found\n2. Offer to help with the task directly using your general capabilities\n3. Suggest the user could create their own skill with `npx skills init`\n\nExample:\n\n```\nI searched for skills related to \"xyz\" but didn't find any matches.\nI can still help you with this task directly! Would you like me to proceed?\n\nIf this is something you do often, you could create your own skill:\nnpx skills init my-xyz-skill\n```\n",".claude/skills/compose-navigation/SKILL.md":"# compose-navigation\n\nPath: `.claude/skills/compose-navigation/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/compose-navigation/SKILL.md)",".claude/skills/coil-compose/SKILL.md":"# coil-compose\n\nPath: `.claude/skills/coil-compose/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/coil-compose/SKILL.md)",".claude/skills/android-viewmodel/SKILL.md":"# android-viewmodel\n\nPath: `.claude/skills/android-viewmodel/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-viewmodel/SKILL.md)",".claude/skills/android-testing/SKILL.md":"# android-testing\n\nPath: `.claude/skills/android-testing/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-testing/SKILL.md)",".claude/skills/android-retrofit/SKILL.md":"# android-retrofit\n\nPath: `.claude/skills/android-retrofit/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-retrofit/SKILL.md)",".claude/skills/android-gradle-logic/SKILL.md":"# android-gradle-logic\n\nPath: `.claude/skills/android-gradle-logic/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-gradle-logic/SKILL.md)",".claude/skills/android-emulator-skill/SKILL.md":"# android-emulator-skill\n\nPath: `.claude/skills/android-emulator-skill/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/SKILL.md)",".claude/skills/android-data-layer/SKILL.md":"# android-data-layer\n\nPath: `.claude/skills/android-data-layer/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-data-layer/SKILL.md)",".claude/skills/android-coroutines/SKILL.md":"# android-coroutines\n\nPath: `.claude/skills/android-coroutines/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-coroutines/SKILL.md)",".claude/skills/android-architecture/SKILL.md":"# android-architecture\n\nPath: `.claude/skills/android-architecture/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-architecture/SKILL.md)",".claude/skills/android-accessibility/SKILL.md":"# android-accessibility\n\nPath: `.claude/skills/android-accessibility/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-accessibility/SKILL.md)","CLAUDE.md":"# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/CLAUDE.md)",".claude/agents/mobile-developer.md":"# Subagent: mobile-developer\n\nPath: `.claude/agents/mobile-developer.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/agents/mobile-developer.md)",".claude/skills/ui-ux-pro-max/scripts/core.py":"# Claude Directive: core.py\n\nPath: `.claude/skills/ui-ux-pro-max/scripts/core.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/scripts/core.py)",".claude/skills/ui-ux-pro-max/scripts/search.py":"# Claude Directive: search.py\n\nPath: `.claude/skills/ui-ux-pro-max/scripts/search.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/scripts/search.py)",".claude/skills/ui-ux-pro-max/data/charts.csv":"# Claude Directive: charts.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/charts.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/charts.csv)",".claude/skills/ui-ux-pro-max/data/colors.csv":"# Claude Directive: colors.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/colors.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/colors.csv)",".claude/skills/ui-ux-pro-max/data/icons.csv":"# Claude Directive: icons.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/icons.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/icons.csv)",".claude/skills/ui-ux-pro-max/data/landing.csv":"# Claude Directive: landing.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/landing.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/landing.csv)",".claude/skills/ui-ux-pro-max/data/products.csv":"# Claude Directive: products.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/products.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/products.csv)",".claude/skills/ui-ux-pro-max/data/prompts.csv":"# Claude Directive: prompts.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/prompts.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/prompts.csv)",".claude/skills/ui-ux-pro-max/data/styles.csv":"# Claude Directive: styles.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/styles.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/styles.csv)",".claude/skills/ui-ux-pro-max/data/typography.csv":"# Claude Directive: typography.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/typography.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/typography.csv)",".claude/skills/ui-ux-pro-max/data/ux-guidelines.csv":"# Claude Directive: ux-guidelines.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv)",".claude/skills/mobile-android-design/references/android-navigation.md":"# References Skill\n\nPath: `.claude/skills/mobile-android-design/references/android-navigation.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/android-navigation.md)",".claude/skills/mobile-android-design/references/compose-components.md":"# References Skill\n\nPath: `.claude/skills/mobile-android-design/references/compose-components.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/compose-components.md)",".claude/skills/mobile-android-design/references/material3-theming.md":"# References Skill\n\nPath: `.claude/skills/mobile-android-design/references/material3-theming.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/material3-theming.md)",".claude/skills/marketing-ideas/references/ideas-by-category.md":"# References Skill\n\nPath: `.claude/skills/marketing-ideas/references/ideas-by-category.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/marketing-ideas/references/ideas-by-category.md)",".claude/skills/kmp/scripts/suggest-kmp-dependency.sh":"# Claude Directive: suggest-kmp-dependency.sh\n\nPath: `.claude/skills/kmp/scripts/suggest-kmp-dependency.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/scripts/suggest-kmp-dependency.sh)",".claude/skills/kmp/scripts/validate-kmp-structure.sh":"# Claude Directive: validate-kmp-structure.sh\n\nPath: `.claude/skills/kmp/scripts/validate-kmp-structure.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/scripts/validate-kmp-structure.sh)",".claude/skills/kmp/references/abstraction-examples.md":"# References Skill\n\nPath: `.claude/skills/kmp/references/abstraction-examples.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/abstraction-examples.md)",".claude/skills/kmp/references/expect-actual-catalog.md":"# References Skill\n\nPath: `.claude/skills/kmp/references/expect-actual-catalog.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/expect-actual-catalog.md)",".claude/skills/kmp/references/source-set-hierarchy.md":"# References Skill\n\nPath: `.claude/skills/kmp/references/source-set-hierarchy.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/source-set-hierarchy.md)",".claude/skills/kmp/references/target-compatibility.md":"# References Skill\n\nPath: `.claude/skills/kmp/references/target-compatibility.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/target-compatibility.md)",".claude/skills/android-emulator-skill/scripts/app_launcher.py":"# Claude Directive: app_launcher.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/app_launcher.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/app_launcher.py)",".claude/skills/android-emulator-skill/scripts/build_and_test.py":"# Claude Directive: build_and_test.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/build_and_test.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/build_and_test.py)",".claude/skills/android-emulator-skill/scripts/common.py":"# Claude Directive: common.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/common.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/common.py)",".claude/skills/android-emulator-skill/scripts/emu_health_check.sh":"# Claude Directive: emu_health_check.sh\n\nPath: `.claude/skills/android-emulator-skill/scripts/emu_health_check.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/emu_health_check.sh)",".claude/skills/android-emulator-skill/scripts/emulator_manage.py":"# Claude Directive: emulator_manage.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/emulator_manage.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/emulator_manage.py)",".claude/skills/android-emulator-skill/scripts/gesture.py":"# Claude Directive: gesture.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/gesture.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/gesture.py)",".claude/skills/android-emulator-skill/scripts/keyboard.py":"# Claude Directive: keyboard.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/keyboard.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/keyboard.py)",".claude/skills/android-emulator-skill/scripts/log_monitor.py":"# Claude Directive: log_monitor.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/log_monitor.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/log_monitor.py)",".claude/skills/android-emulator-skill/scripts/navigator.py":"# Claude Directive: navigator.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/navigator.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/navigator.py)",".claude/skills/android-emulator-skill/scripts/screen_mapper.py":"# Claude Directive: screen_mapper.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/screen_mapper.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/screen_mapper.py)",".claude/skills/ui-ux-pro-max/data/stacks/flutter.csv":"# Claude Directive: flutter.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv)",".claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv":"# Claude Directive: html-tailwind.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv)",".claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv":"# Claude Directive: nextjs.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv)",".claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv":"# Claude Directive: nuxt-ui.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv)",".claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv":"# Claude Directive: nuxtjs.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv)",".claude/skills/ui-ux-pro-max/data/stacks/react.csv":"# Claude Directive: react.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/react.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/react.csv)",".claude/skills/ui-ux-pro-max/data/stacks/react-native.csv":"# Claude Directive: react-native.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv)",".claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv":"# Claude Directive: shadcn.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv)",".claude/skills/ui-ux-pro-max/data/stacks/svelte.csv":"# Claude Directive: svelte.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv)",".claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv":"# Claude Directive: swiftui.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv)",".claude/skills/ui-ux-pro-max/data/stacks/vue.csv":"# Claude Directive: vue.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/vue.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv)"},"files":{".claude/skills/kotlin-concurrency-expert/SKILL.md":"---\nname: kotlin-concurrency-expert\ndescription: Kotlin Coroutines review and remediation for Android. Use when asked to review concurrency usage, fix coroutine-related bugs, improve thread safety, or resolve lifecycle issues in Kotlin/Android code.\n---\n\n# Kotlin Concurrency Expert\n\n## Overview\n\nReview and fix Kotlin Coroutines issues in Android codebases by applying structured concurrency, lifecycle safety, proper scoping, and modern best practices with minimal behavior changes.\n\n## Workflow\n\n### 1. Triage the Issue\n\n- Capture the exact error, crash, or symptom (ANR, memory leak, race condition, incorrect state).\n- Check project coroutines setup: `kotlinx-coroutines-android` version, `lifecycle-runtime-ktx` version.\n- Identify the current scope context (`viewModelScope`, `lifecycleScope`, custom scope, or none).\n- Confirm whether the code is UI-bound (`Dispatchers.Main`) or intended to run off the main thread (`Dispatchers.IO`, `Dispatchers.Default`).\n- Verify Dispatcher injection patterns for testability.\n\n### 2. Apply the Smallest Safe Fix\n\nPrefer edits that preserve existing behavior while satisfying structured concurrency and lifecycle safety.\n\nCommon fixes:\n\n- **ANR / Main thread blocking**: Move heavy work to `withContext(Dispatchers.IO)` or `Dispatchers.Default`; ensure suspend functions are main-safe.\n- **Memory leaks / zombie coroutines**: Replace `GlobalScope` with a lifecycle-bound scope (`viewModelScope`, `lifecycleScope`, or injected `applicationScope`).\n- **Lifecycle collection issues**: Replace deprecated `launchWhenStarted` with `repeatOnLifecycle(Lifecycle.State.STARTED)`.\n- **State exposure**: Encapsulate `MutableStateFlow` / `MutableSharedFlow`; expose read-only `StateFlow` or `Flow`.\n- **CancellationException swallowing**: Ensure generic `catch (e: Exception)` blocks rethrow `CancellationException`.\n- **Non-cooperative cancellation**: Add `ensureActive()` or `yield()` in tight loops for cooperative cancellation.\n- **Callback APIs**: Convert listeners to `callbackFlow` with proper `awaitClose` cleanup.\n- **Hardcoded Dispatchers**: Inject `CoroutineDispatcher` via constructor for testability.\n\n## Critical Rules\n\n### Dispatcher Injection (Testability)\n\n```kotlin\n// CORRECT: Inject dispatcher\nclass UserRepository(\n    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO\n) {\n    suspend fun fetchUser() = withContext(ioDispatcher) { ... }\n}\n\n// INCORRECT: Hardcoded dispatcher\nclass UserRepository {\n    suspend fun fetchUser() = withContext(Dispatchers.IO) { ... }\n}\n```\n\n### Lifecycle-Aware Collection\n\n```kotlin\n// CORRECT: Use repeatOnLifecycle\nviewLifecycleOwner.lifecycleScope.launch {\n    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {\n        viewModel.uiState.collect { state -> updateUI(state) }\n    }\n}\n\n// INCORRECT: Direct collection (unsafe, deprecated)\nlifecycleScope.launchWhenStarted {\n    viewModel.uiState.collect { state -> updateUI(state) }\n}\n```\n\n### State Encapsulation\n\n```kotlin\n// CORRECT: Expose read-only StateFlow\nclass MyViewModel : ViewModel() {\n    private val _uiState = MutableStateFlow(UiState())\n    val uiState: StateFlow<UiState> = _uiState.asStateFlow()\n}\n\n// INCORRECT: Exposed mutable state\nclass MyViewModel : ViewModel() {\n    val uiState = MutableStateFlow(UiState()) // Leaks mutability\n}\n```\n\n### Exception Handling\n\n```kotlin\n// CORRECT: Rethrow CancellationException\ntry {\n    doSuspendWork()\n} catch (e: CancellationException) {\n    throw e // Must rethrow!\n} catch (e: Exception) {\n    handleError(e)\n}\n\n// INCORRECT: Swallows cancellation\ntry {\n    doSuspendWork()\n} catch (e: Exception) {\n    handleError(e) // CancellationException swallowed!\n}\n```\n\n### Cooperative Cancellation\n\n```kotlin\n// CORRECT: Check for cancellation in tight loops\nsuspend fun processLargeList(items: List<Item>) {\n    items.forEach { item ->\n        ensureActive() // Check cancellation\n        processItem(item)\n    }\n}\n\n// INCORRECT: Non-cooperative (ignores cancellation)\nsuspend fun processLargeList(items: List<Item>) {\n    items.forEach { item ->\n        processItem(item) // Never checks cancellation\n    }\n}\n```\n\n### Callback Conversion\n\n```kotlin\n// CORRECT: callbackFlow with awaitClose\nfun locationUpdates(): Flow<Location> = callbackFlow {\n    val listener = LocationListener { location ->\n        trySend(location)\n    }\n    locationManager.requestLocationUpdates(listener)\n    \n    awaitClose { locationManager.removeUpdates(listener) }\n}\n```\n\n## Scope Guidelines\n\n| Scope | Use When | Lifecycle |\n|-------|----------|-----------|\n| `viewModelScope` | ViewModel operations | Cleared with ViewModel |\n| `lifecycleScope` | UI operations in Activity/Fragment | Destroyed with lifecycle owner |\n| `repeatOnLifecycle` | Flow collection in UI | Started/Stopped with lifecycle state |\n| `applicationScope` (injected) | App-wide background work | Application lifetime |\n| `GlobalScope` | **NEVER USE** | Breaks structured concurrency |\n\n## Testing Pattern\n\n```kotlin\n@Test\nfun `loading data updates state`() = runTest {\n    val testDispatcher = StandardTestDispatcher(testScheduler)\n    val repository = FakeRepository()\n    val viewModel = MyViewModel(repository, testDispatcher)\n    \n    viewModel.loadData()\n    advanceUntilIdle()\n    \n    assertEquals(UiState.Success(data), viewModel.uiState.value)\n}\n```\n\n## Reference Material\n\n- [Kotlin Coroutines Best Practices](https://developer.android.com/kotlin/coroutines/coroutines-best-practices)\n- [StateFlow and SharedFlow](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow)\n- [repeatOnLifecycle API](https://developer.android.com/topic/libraries/architecture/coroutines#repeatOnLifecycle)\n",".claude/skills/marketing-ideas/SKILL.md":"---\nname: marketing-ideas\nversion: 1.0.0\ndescription: \"When the user needs marketing ideas, inspiration, or strategies for their SaaS or software product. Also use when the user asks for 'marketing ideas,' 'growth ideas,' 'how to market,' 'marketing strategies,' 'marketing tactics,' 'ways to promote,' or 'ideas to grow.' This skill provides 139 proven marketing approaches organized by category.\"\n---\n\n# Marketing Ideas for SaaS\n\nYou are a marketing strategist with a library of 139 proven marketing ideas. Your goal is to help users find the right marketing strategies for their specific situation, stage, and resources.\n\n## How to Use This Skill\n\n**Check for product marketing context first:**\nIf `.claude/product-marketing-context.md` exists, read it before asking questions. Use that context and only ask for information not already covered or specific to this task.\n\nWhen asked for marketing ideas:\n1. Ask about their product, audience, and current stage if not clear\n2. Suggest 3-5 most relevant ideas based on their context\n3. Provide details on implementation for chosen ideas\n4. Consider their resources (time, budget, team size)\n\n---\n\n## Ideas by Category (Quick Reference)\n\n| Category | Ideas | Examples |\n|----------|-------|----------|\n| Content & SEO | 1-10 | Programmatic SEO, Glossary marketing, Content repurposing |\n| Competitor | 11-13 | Comparison pages, Marketing jiu-jitsu |\n| Free Tools | 14-22 | Calculators, Generators, Chrome extensions |\n| Paid Ads | 23-34 | LinkedIn, Google, Retargeting, Podcast ads |\n| Social & Community | 35-44 | LinkedIn audience, Reddit marketing, Short-form video |\n| Email | 45-53 | Founder emails, Onboarding sequences, Win-back |\n| Partnerships | 54-64 | Affiliate programs, Integration marketing, Newsletter swaps |\n| Events | 65-72 | Webinars, Conference speaking, Virtual summits |\n| PR & Media | 73-76 | Press coverage, Documentaries |\n| Launches | 77-86 | Product Hunt, Lifetime deals, Giveaways |\n| Product-Led | 87-96 | Viral loops, Powered-by marketing, Free migrations |\n| Content Formats | 97-109 | Podcasts, Courses, Annual reports, Year wraps |\n| Unconventional | 110-122 | Awards, Challenges, Guerrilla marketing |\n| Platforms | 123-130 | App marketplaces, Review sites, YouTube |\n| International | 131-132 | Expansion, Price localization |\n| Developer | 133-136 | DevRel, Certifications |\n| Audience-Specific | 137-139 | Referrals, Podcast tours, Customer language |\n\n**For the complete list with descriptions**: See [references/ideas-by-category.md](references/ideas-by-category.md)\n\n---\n\n## Implementation Tips\n\n### By Stage\n\n**Pre-launch:**\n- Waitlist referrals (#79)\n- Early access pricing (#81)\n- Product Hunt prep (#78)\n\n**Early stage:**\n- Content & SEO (#1-10)\n- Community (#35)\n- Founder-led sales (#47)\n\n**Growth stage:**\n- Paid acquisition (#23-34)\n- Partnerships (#54-64)\n- Events (#65-72)\n\n**Scale:**\n- Brand campaigns\n- International (#131-132)\n- Media acquisitions (#73)\n\n### By Budget\n\n**Free:**\n- Content & SEO\n- Community building\n- Social media\n- Comment marketing\n\n**Low budget:**\n- Targeted ads\n- Sponsorships\n- Free tools\n\n**Medium budget:**\n- Events\n- Partnerships\n- PR\n\n**High budget:**\n- Acquisitions\n- Conferences\n- Brand campaigns\n\n### By Timeline\n\n**Quick wins:**\n- Ads, email, social posts\n\n**Medium-term:**\n- Content, SEO, community\n\n**Long-term:**\n- Brand, thought leadership, platform effects\n\n---\n\n## Top Ideas by Use Case\n\n### Need Leads Fast\n- Google Ads (#31) - High-intent search\n- LinkedIn Ads (#28) - B2B targeting\n- Engineering as Marketing (#15) - Free tool lead gen\n\n### Building Authority\n- Conference Speaking (#70)\n- Book Marketing (#104)\n- Podcasts (#107)\n\n### Low Budget Growth\n- Easy Keyword Ranking (#1)\n- Reddit Marketing (#38)\n- Comment Marketing (#44)\n\n### Product-Led Growth\n- Viral Loops (#93)\n- Powered By Marketing (#87)\n- In-App Upsells (#91)\n\n### Enterprise Sales\n- Investor Marketing (#133)\n- Expert Networks (#57)\n- Conference Sponsorship (#72)\n\n---\n\n## Output Format\n\nWhen recommending ideas, provide for each:\n\n- **Idea name**: One-line description\n- **Why it fits**: Connection to their situation\n- **How to start**: First 2-3 implementation steps\n- **Expected outcome**: What success looks like\n- **Resources needed**: Time, budget, skills required\n\n---\n\n## Task-Specific Questions\n\n1. What's your current stage and main growth goal?\n2. What's your marketing budget and team size?\n3. What have you already tried that worked or didn't?\n4. What competitor tactics do you admire?\n\n---\n\n## Related Skills\n\n- **programmatic-seo**: For scaling SEO content (#4)\n- **competitor-alternatives**: For comparison pages (#11)\n- **email-sequence**: For email marketing tactics\n- **free-tool-strategy**: For engineering as marketing (#15)\n- **referral-program**: For viral growth (#93)\n",".claude/skills/xml-to-compose-migration/SKILL.md":"---\nname: xml-to-compose-migration\ndescription: Convert Android XML layouts to Jetpack Compose. Use when asked to migrate Views to Compose, convert XML to Composables, or modernize UI from View system to Compose.\n---\n\n# XML to Compose Migration\n\n## Overview\n\nSystematically convert Android XML layouts to idiomatic Jetpack Compose, preserving functionality while embracing Compose patterns. This skill covers layout mapping, state migration, and incremental adoption strategies.\n\n## Workflow\n\n### 1. Analyze the XML Layout\n\n- Identify the root layout type (`ConstraintLayout`, `LinearLayout`, `FrameLayout`, etc.).\n- List all View widgets and their key attributes.\n- Map data binding expressions (`@{}`) or view binding references.\n- Identify custom views that need special handling.\n- Note any `include`, `merge`, or `ViewStub` usage.\n\n### 2. Plan the Migration\n\n- Decide: **Full rewrite** or **incremental migration** (using `ComposeView`/`AndroidView`).\n- Identify state sources (ViewModel, LiveData, savedInstanceState).\n- List reusable components to extract as separate Composables.\n- Plan navigation integration if using Navigation component.\n\n### 3. Convert Layouts\n\nApply the layout mapping table below to convert each View to its Compose equivalent.\n\n### 4. Migrate State\n\n- Convert `LiveData` observation to `StateFlow` collection or `observeAsState()`.\n- Replace `findViewById` / ViewBinding with Compose state.\n- Convert click listeners to lambda parameters.\n\n### 5. Test and Verify\n\n- Compare visual output between XML and Compose versions.\n- Test accessibility (content descriptions, touch targets).\n- Verify state preservation across configuration changes.\n\n---\n\n## Layout Mapping Reference\n\n### Container Layouts\n\n| XML Layout | Compose Equivalent | Notes |\n|------------|-------------------|-------|\n| `LinearLayout (vertical)` | `Column` | Use `Arrangement` and `Alignment` |\n| `LinearLayout (horizontal)` | `Row` | Use `Arrangement` and `Alignment` |\n| `FrameLayout` | `Box` | Children stack on top of each other |\n| `ConstraintLayout` | `ConstraintLayout` (Compose) | Use `createRefs()` and `constrainAs` |\n| `RelativeLayout` | `Box` or `ConstraintLayout` | Prefer Box for simple overlap |\n| `ScrollView` | `Column` + `Modifier.verticalScroll()` | Or use `LazyColumn` for lists |\n| `HorizontalScrollView` | `Row` + `Modifier.horizontalScroll()` | Or use `LazyRow` for lists |\n| `RecyclerView` | `LazyColumn` / `LazyRow` / `LazyGrid` | Most common migration |\n| `ViewPager2` | `HorizontalPager` | From accompanist or Compose Foundation |\n| `CoordinatorLayout` | Custom + `Scaffold` | Use `TopAppBar` with scroll behavior |\n| `NestedScrollView` | `Column` + `Modifier.verticalScroll()` | Prefer Lazy variants |\n\n### Common Widgets\n\n| XML Widget | Compose Equivalent | Notes |\n|------------|-------------------|-------|\n| `TextView` | `Text` | Use `style` → `TextStyle` |\n| `EditText` | `TextField` / `OutlinedTextField` | Requires state hoisting |\n| `Button` | `Button` | Use `onClick` lambda |\n| `ImageView` | `Image` | Use `painterResource()` or Coil |\n| `ImageButton` | `IconButton` | Use `Icon` inside |\n| `CheckBox` | `Checkbox` | Requires `checked` + `onCheckedChange` |\n| `RadioButton` | `RadioButton` | Use with `Row` for groups |\n| `Switch` | `Switch` | Requires state hoisting |\n| `ProgressBar (circular)` | `CircularProgressIndicator` | |\n| `ProgressBar (horizontal)` | `LinearProgressIndicator` | |\n| `SeekBar` | `Slider` | Requires state hoisting |\n| `Spinner` | `DropdownMenu` + `ExposedDropdownMenuBox` | More complex pattern |\n| `CardView` | `Card` | From Material 3 |\n| `Toolbar` | `TopAppBar` | Use inside `Scaffold` |\n| `BottomNavigationView` | `NavigationBar` | Material 3 |\n| `FloatingActionButton` | `FloatingActionButton` | Use inside `Scaffold` |\n| `Divider` | `HorizontalDivider` / `VerticalDivider` | |\n| `Space` | `Spacer` | Use `Modifier.size()` |\n\n### Attribute Mapping\n\n| XML Attribute | Compose Modifier/Property |\n|---------------|--------------------------|\n| `android:layout_width=\"match_parent\"` | `Modifier.fillMaxWidth()` |\n| `android:layout_height=\"match_parent\"` | `Modifier.fillMaxHeight()` |\n| `android:layout_width=\"wrap_content\"` | `Modifier.wrapContentWidth()` (usually implicit) |\n| `android:layout_weight` | `Modifier.weight(1f)` |\n| `android:padding` | `Modifier.padding()` |\n| `android:layout_margin` | `Modifier.padding()` on parent, or use `Arrangement.spacedBy()` |\n| `android:background` | `Modifier.background()` |\n| `android:visibility=\"gone\"` | Conditional composition (don't emit) |\n| `android:visibility=\"invisible\"` | `Modifier.alpha(0f)` (keeps space) |\n| `android:clickable` | `Modifier.clickable { }` |\n| `android:contentDescription` | `Modifier.semantics { contentDescription = \"\" }` |\n| `android:elevation` | `Modifier.shadow()` or component's `elevation` param |\n| `android:alpha` | `Modifier.alpha()` |\n| `android:rotation` | `Modifier.rotate()` |\n| `android:scaleX/Y` | `Modifier.scale()` |\n| `android:gravity` | `Alignment` parameter or `Arrangement` |\n| `android:layout_gravity` | `Modifier.align()` |\n\n---\n\n## Common Patterns\n\n### LinearLayout with Weights\n\n```xml\n<!-- XML -->\n<LinearLayout android:orientation=\"horizontal\">\n    <View android:layout_weight=\"1\" />\n    <View android:layout_weight=\"2\" />\n</LinearLayout>\n```\n\n```kotlin\n// Compose\nRow(modifier = Modifier.fillMaxWidth()) {\n    Box(modifier = Modifier.weight(1f))\n    Box(modifier = Modifier.weight(2f))\n}\n```\n\n### RecyclerView to LazyColumn\n\n```xml\n<!-- XML -->\n<androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/recyclerView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />\n```\n\n```kotlin\n// Compose\nLazyColumn(modifier = Modifier.fillMaxSize()) {\n    items(items, key = { it.id }) { item ->\n        ItemRow(item = item, onClick = { onItemClick(item) })\n    }\n}\n```\n\n### EditText with Two-Way Binding\n\n```xml\n<!-- XML with Data Binding -->\n<EditText\n    android:text=\"@={viewModel.username}\"\n    android:hint=\"@string/username_hint\" />\n```\n\n```kotlin\n// Compose\nval username by viewModel.username.collectAsState()\n\nOutlinedTextField(\n    value = username,\n    onValueChange = { viewModel.updateUsername(it) },\n    label = { Text(stringResource(R.string.username_hint)) },\n    modifier = Modifier.fillMaxWidth()\n)\n```\n\n### ConstraintLayout Migration\n\n```xml\n<!-- XML -->\n<androidx.constraintlayout.widget.ConstraintLayout>\n    <TextView\n        android:id=\"@+id/title\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\" />\n    <TextView\n        android:id=\"@+id/subtitle\"\n        app:layout_constraintTop_toBottomOf=\"@id/title\"\n        app:layout_constraintStart_toStartOf=\"@id/title\" />\n</androidx.constraintlayout.widget.ConstraintLayout>\n```\n\n```kotlin\n// Compose\nConstraintLayout(modifier = Modifier.fillMaxWidth()) {\n    val (title, subtitle) = createRefs()\n    \n    Text(\n        text = \"Title\",\n        modifier = Modifier.constrainAs(title) {\n            top.linkTo(parent.top)\n            start.linkTo(parent.start)\n        }\n    )\n    Text(\n        text = \"Subtitle\", \n        modifier = Modifier.constrainAs(subtitle) {\n            top.linkTo(title.bottom)\n            start.linkTo(title.start)\n        }\n    )\n}\n```\n\n### Include / Merge → Extract Composable\n\n```xml\n<!-- XML: layout_header.xml -->\n<merge>\n    <ImageView android:id=\"@+id/avatar\" />\n    <TextView android:id=\"@+id/name\" />\n</merge>\n\n<!-- Usage -->\n<include layout=\"@layout/layout_header\" />\n```\n\n```kotlin\n// Compose: Extract as a reusable Composable\n@Composable\nfun HeaderSection(\n    avatarUrl: String,\n    name: String,\n    modifier: Modifier = Modifier\n) {\n    Row(modifier = modifier) {\n        AsyncImage(model = avatarUrl, contentDescription = null)\n        Text(text = name)\n    }\n}\n\n// Usage\nHeaderSection(avatarUrl = user.avatar, name = user.name)\n```\n\n---\n\n## Incremental Migration (Interop)\n\n### Embedding Compose in XML\n\n```xml\n<!-- In your XML layout -->\n<androidx.compose.ui.platform.ComposeView\n    android:id=\"@+id/compose_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\" />\n```\n\n```kotlin\n// In Fragment/Activity\nbinding.composeView.setContent {\n    MaterialTheme {\n        MyComposable()\n    }\n}\n```\n\n### Embedding XML Views in Compose\n\n```kotlin\n// Use AndroidView for Views that don't have Compose equivalents\n@Composable\nfun MapViewComposable(modifier: Modifier = Modifier) {\n    AndroidView(\n        factory = { context ->\n            MapView(context).apply {\n                // Initialize the view\n            }\n        },\n        update = { mapView ->\n            // Update the view when state changes\n        },\n        modifier = modifier\n    )\n}\n```\n\n---\n\n## State Migration\n\n### LiveData to Compose\n\n```kotlin\n// Before: Observing in Fragment\nviewModel.uiState.observe(viewLifecycleOwner) { state ->\n    binding.title.text = state.title\n}\n\n// After: Collecting in Compose\n@Composable\nfun MyScreen(viewModel: MyViewModel = hiltViewModel()) {\n    val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n    \n    Text(text = uiState.title)\n}\n```\n\n### Click Listeners\n\n```kotlin\n// Before: XML + setOnClickListener\nbinding.submitButton.setOnClickListener {\n    viewModel.submit()\n}\n\n// After: Lambda in Compose\nButton(onClick = { viewModel.submit() }) {\n    Text(\"Submit\")\n}\n```\n\n---\n\n## Checklist\n\n- [ ] All layouts converted (no `include` or `merge` left)\n- [ ] State hoisted properly (no internal mutable state for user input)\n- [ ] Click handlers converted to lambdas\n- [ ] RecyclerView adapters removed (using LazyColumn/LazyRow)\n- [ ] ViewBinding/DataBinding removed\n- [ ] Navigation integrated (NavHost or interop)\n- [ ] Theming applied (MaterialTheme)\n- [ ] Accessibility preserved (content descriptions, touch targets)\n- [ ] Preview annotations added for development\n- [ ] Old XML files deleted\n\n## References\n\n- [Interoperability APIs](https://developer.android.com/develop/ui/compose/migrate/interoperability-apis)\n- [Migration Strategy](https://developer.android.com/develop/ui/compose/migrate/strategy)\n- [Compose and Views side by side](https://developer.android.com/develop/ui/compose/migrate)\n",".claude/skills/jetpack-compose/SKILL.md":"---\nname: android-jetpack-compose\ndescription: Use when building Android UIs with Jetpack Compose, managing state with remember/mutableStateOf, or implementing declarative UI patterns.\nallowed-tools:\n  - Read\n  - Write\n  - Edit\n  - Bash\n  - Grep\n  - Glob\n---\n\n# Android - Jetpack Compose\n\nModern declarative UI toolkit for building native Android interfaces.\n\n## Key Concepts\n\n### State Management\n\nCompose provides several ways to manage state:\n\n- **remember**: Survives recomposition\n- **rememberSaveable**: Survives configuration changes\n- **mutableStateOf**: Creates observable state\n- **derivedStateOf**: Computed state that updates when dependencies change\n\n```kotlin\n@Composable\nfun Counter() {\n    var count by remember { mutableStateOf(0) }\n\n    Column {\n        Text(\"Count: $count\")\n        Button(onClick = { count++ }) {\n            Text(\"Increment\")\n        }\n    }\n}\n\n// With saveable for configuration changes\n@Composable\nfun SearchField() {\n    var query by rememberSaveable { mutableStateOf(\"\") }\n\n    TextField(\n        value = query,\n        onValueChange = { query = it },\n        placeholder = { Text(\"Search...\") }\n    )\n}\n```\n\n### State Hoisting\n\nLift state up to make composables stateless and reusable:\n\n```kotlin\n// Stateless composable\n@Composable\nfun NameInput(\n    name: String,\n    onNameChange: (String) -> Unit,\n    modifier: Modifier = Modifier\n) {\n    TextField(\n        value = name,\n        onValueChange = onNameChange,\n        label = { Text(\"Name\") },\n        modifier = modifier\n    )\n}\n\n// Stateful parent\n@Composable\nfun UserForm() {\n    var name by remember { mutableStateOf(\"\") }\n\n    NameInput(\n        name = name,\n        onNameChange = { name = it }\n    )\n}\n```\n\n### ViewModel Integration\n\n```kotlin\nclass UserViewModel : ViewModel() {\n    private val _uiState = MutableStateFlow(UserUiState())\n    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()\n\n    fun updateName(name: String) {\n        _uiState.update { it.copy(name = name) }\n    }\n\n    fun saveUser() {\n        viewModelScope.launch {\n            _uiState.update { it.copy(isLoading = true) }\n            try {\n                userRepository.save(_uiState.value.toUser())\n                _uiState.update { it.copy(isLoading = false, isSaved = true) }\n            } catch (e: Exception) {\n                _uiState.update { it.copy(isLoading = false, error = e.message) }\n            }\n        }\n    }\n}\n\n@Composable\nfun UserScreen(viewModel: UserViewModel = viewModel()) {\n    val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n\n    UserContent(\n        uiState = uiState,\n        onNameChange = viewModel::updateName,\n        onSave = viewModel::saveUser\n    )\n}\n```\n\n## Best Practices\n\n### Composable Function Guidelines\n\n```kotlin\n// Use Modifier as first optional parameter\n@Composable\nfun CustomCard(\n    title: String,\n    modifier: Modifier = Modifier,\n    onClick: () -> Unit = {}\n) {\n    Card(\n        modifier = modifier.clickable(onClick = onClick)\n    ) {\n        Text(\n            text = title,\n            modifier = Modifier.padding(16.dp)\n        )\n    }\n}\n\n// Use slot APIs for flexible content\n@Composable\nfun CustomScaffold(\n    topBar: @Composable () -> Unit = {},\n    bottomBar: @Composable () -> Unit = {},\n    content: @Composable (PaddingValues) -> Unit\n) {\n    Scaffold(\n        topBar = topBar,\n        bottomBar = bottomBar,\n        content = content\n    )\n}\n```\n\n### Efficient Recomposition\n\n```kotlin\n// Use keys for list items\n@Composable\nfun UserList(users: List<User>) {\n    LazyColumn {\n        items(\n            items = users,\n            key = { it.id }  // Stable key for efficient updates\n        ) { user ->\n            UserItem(user)\n        }\n    }\n}\n\n// Use derivedStateOf for expensive computations\n@Composable\nfun FilteredList(items: List<Item>, query: String) {\n    val filteredItems by remember(items, query) {\n        derivedStateOf {\n            items.filter { it.name.contains(query, ignoreCase = true) }\n        }\n    }\n\n    LazyColumn {\n        items(filteredItems) { item ->\n            ItemRow(item)\n        }\n    }\n}\n```\n\n### Side Effects\n\n```kotlin\n// LaunchedEffect for coroutine-based side effects\n@Composable\nfun UserProfile(userId: String, viewModel: UserViewModel) {\n    LaunchedEffect(userId) {\n        viewModel.loadUser(userId)\n    }\n\n    // UI content\n}\n\n// DisposableEffect for cleanup\n@Composable\nfun LifecycleAwareComponent(lifecycle: Lifecycle) {\n    DisposableEffect(lifecycle) {\n        val observer = LifecycleEventObserver { _, event ->\n            // Handle lifecycle events\n        }\n        lifecycle.addObserver(observer)\n\n        onDispose {\n            lifecycle.removeObserver(observer)\n        }\n    }\n}\n\n// SideEffect for non-suspend side effects\n@Composable\nfun AnalyticsScreen(screenName: String) {\n    SideEffect {\n        analytics.logScreenView(screenName)\n    }\n}\n```\n\n## Common Patterns\n\n### Navigation with Navigation Compose\n\n```kotlin\n@Composable\nfun AppNavigation() {\n    val navController = rememberNavController()\n\n    NavHost(navController = navController, startDestination = \"home\") {\n        composable(\"home\") {\n            HomeScreen(\n                onNavigateToDetail = { id ->\n                    navController.navigate(\"detail/$id\")\n                }\n            )\n        }\n        composable(\n            route = \"detail/{itemId}\",\n            arguments = listOf(navArgument(\"itemId\") { type = NavType.StringType })\n        ) { backStackEntry ->\n            val itemId = backStackEntry.arguments?.getString(\"itemId\")\n            DetailScreen(itemId = itemId)\n        }\n    }\n}\n```\n\n### Material 3 Theming\n\n```kotlin\n@Composable\nfun AppTheme(\n    darkTheme: Boolean = isSystemInDarkTheme(),\n    content: @Composable () -> Unit\n) {\n    val colorScheme = when {\n        darkTheme -> darkColorScheme(\n            primary = Purple80,\n            secondary = PurpleGrey80,\n            tertiary = Pink80\n        )\n        else -> lightColorScheme(\n            primary = Purple40,\n            secondary = PurpleGrey40,\n            tertiary = Pink40\n        )\n    }\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        typography = Typography,\n        content = content\n    )\n}\n\n// Using theme values\n@Composable\nfun ThemedCard() {\n    Card(\n        colors = CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surfaceVariant\n        )\n    ) {\n        Text(\n            text = \"Themed content\",\n            style = MaterialTheme.typography.bodyLarge,\n            color = MaterialTheme.colorScheme.onSurfaceVariant\n        )\n    }\n}\n```\n\n### Lists and Grids\n\n```kotlin\n@Composable\nfun ProductGrid(products: List<Product>) {\n    LazyVerticalGrid(\n        columns = GridCells.Adaptive(minSize = 160.dp),\n        contentPadding = PaddingValues(16.dp),\n        horizontalArrangement = Arrangement.spacedBy(16.dp),\n        verticalArrangement = Arrangement.spacedBy(16.dp)\n    ) {\n        items(products, key = { it.id }) { product ->\n            ProductCard(product)\n        }\n    }\n}\n\n// Sticky headers\n@Composable\nfun ContactList(contacts: Map<Char, List<Contact>>) {\n    LazyColumn {\n        contacts.forEach { (initial, contactsForInitial) ->\n            stickyHeader {\n                Text(\n                    text = initial.toString(),\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .background(MaterialTheme.colorScheme.surface)\n                        .padding(16.dp),\n                    style = MaterialTheme.typography.titleMedium\n                )\n            }\n            items(contactsForInitial) { contact ->\n                ContactItem(contact)\n            }\n        }\n    }\n}\n```\n\n## Anti-Patterns\n\n### Avoid Side Effects in Composition\n\nBad:\n\n```kotlin\n@Composable\nfun BadExample(viewModel: ViewModel) {\n    viewModel.loadData()  // Called on every recomposition!\n\n    Text(\"Data loaded\")\n}\n```\n\nGood:\n\n```kotlin\n@Composable\nfun GoodExample(viewModel: ViewModel) {\n    LaunchedEffect(Unit) {\n        viewModel.loadData()\n    }\n\n    Text(\"Data loaded\")\n}\n```\n\n### Don't Read State in Remember Block\n\nBad:\n\n```kotlin\n@Composable\nfun BadCounter(initial: Int) {\n    // Won't update when initial changes\n    var count by remember { mutableStateOf(initial) }\n}\n```\n\nGood:\n\n```kotlin\n@Composable\nfun GoodCounter(initial: Int) {\n    var count by remember(initial) { mutableStateOf(initial) }\n}\n```\n\n### Avoid Heavy Computation During Composition\n\nBad:\n\n```kotlin\n@Composable\nfun BadList(items: List<Item>) {\n    // Runs on every recomposition\n    val sorted = items.sortedBy { it.name }\n    LazyColumn { /* ... */ }\n}\n```\n\nGood:\n\n```kotlin\n@Composable\nfun GoodList(items: List<Item>) {\n    val sorted by remember(items) {\n        derivedStateOf { items.sortedBy { it.name } }\n    }\n    LazyColumn { /* ... */ }\n}\n```\n\n## Related Skills\n\n- **android-architecture**: MVVM and clean architecture patterns\n- **android-kotlin-coroutines**: Async operations in Compose\n",".claude/skills/simpmusic-icons/SKILL.md":"---\nname: simpmusic-icons\ndescription: Add or change an icon in SimpMusic. Every icon is a generated Material Symbols ImageVector under ui/icon, addressed as SimpIcons.<Name> — there is no material-icons dependency and no XML icon drawable. Use when adding a new icon, replacing one, or hitting ImageVector/Painter type errors.\n---\n\n# SimpMusic Icons\n\nIcons live in `composeApp/src/commonMain/kotlin/com/maxrave/simpmusic/ui/icon/`, one file per\nicon, each declaring `val SimpIcons.<Name>: ImageVector`.\n\nTwo things are deliberately absent and must not come back:\n- the `material-icons-core` / `material-icons-extended` dependency (`Icons.Rounded.*`)\n- XML icon drawables in `composeResources/drawable` used through `painterResource(...)`\n\n## Adding an icon\n\nGoogle's font service generates the Compose file directly — no conversion tool needed:\n\n```bash\ncurl -sfL --compressed \\\n  \"https://fonts.gstatic.com/render/v1/Material+Symbols+Rounded/24dp/<symbol_name>.kt?var=opsz,wght,FILL,GRAD,ROND@24,400,1,0,50\" \\\n  -o <PascalName>.kt\n```\n\nKeep these axes identical for every icon, or the set stops looking like one set:\n**Rounded, opsz 24, wght 400, GRAD 0, ROND 50**, `FILL=1`.\n\nUse `FILL=0` **only** for the \"off\" half of a state pair — `FavoriteBorder`,\n`AddCircleOutline`, `DownloadForOfflineOutlined`. At `FILL=1` the off state renders identically to\nthe on state and the distinction is lost.\n\nThen edit the downloaded file:\n\n1. `package com.example.test` → `package com.maxrave.simpmusic.ui.icon`\n2. `public val <symbol_name>: ImageVector` → `val SimpIcons.<PascalName>: ImageVector`\n3. Rename the backing field `_<symbol_name>` → `_<PascalName>`, and `name = \"<symbol_name>\"` → `\"<PascalName>\"`\n4. Never add `autoMirror = true` to `ImageVector.Builder(...)`: in right-to-left languages only text\n   flips, icons keep their shape (owner's rule, 2026-10-14). `ArrowForwardIos`, `QueueMusic`, `Sort`,\n   `TrendingUp`, `VolumeOff` and `VolumeUp` had it and drew mirrored until it was removed.\n5. Replace the closing `.build()` with `.buildSimpIcon()` (in `SimpIcons.kt`): it is `build()` plus\n   the fit into Material's 20×20 live area described under Traps. `SimpIconsFitTest` fails on a\n   file that still ends with `.build()`.\n\n## Using an icon\n\n```kotlin\nimport com.maxrave.simpmusic.ui.icon.PlayArrow   // per-icon import, required\nimport com.maxrave.simpmusic.ui.icon.SimpIcons\n\nIcon(SimpIcons.PlayArrow, contentDescription = null)\n```\n\nThe project's own icon buttons take an `ImageVector`:\n`RippleIconButton(imageVector = …)`, `LiquidGlassIconButton(imageVector = …)`,\n`ActionButton(icon = …)`.\n\n## Traps\n\n**Every icon needs its own import.** `val SimpIcons.X` is an *extension property*; importing the\n`SimpIcons` object alone leaves it unresolved. This is the same mechanism that lets R8 strip unused\nicons, so never \"simplify\" the set into a `map` or a `when` — that would ship all of them.\n\n**`ImageVector` is not a `Painter`.** `Icon` and `Image` have overloads for both, so those look\nfine. These do not, and need `rememberVectorPainter(SimpIcons.X)`:\n- `AsyncImage(placeholder = …, error = …)`\n- anything drawn inside a `DrawScope` (`with(painter) { draw(...) }`)\n- custom composables whose parameter is typed `Painter`\n\n**Do not text-replace blindly.** A regex swap of `Res.drawable.X` → `SimpIcons.X` produced 40\ncompile errors in one pass: it also hit `Painter` parameters, and it landed *inside* multi-line\n`painterResource(\\n  Res.drawable.X,\\n)` calls, leaving the syntactically valid but wrong\n`painterResource(SimpIcons.X)`.\n\n**Do not replace an icon whose colour carries meaning.** These stay as resources:\n\n| File | Why |\n|---|---|\n| `baseline_downloaded.xml` | `#FF00A0CB` — the blue *is* the \"downloaded\" state |\n| `baseline_favorite_24.xml` | `#D10000` — the red *is* the \"liked\" state |\n| `mono.xml`, `monochrome.xml` | SimpMusic logos (`#0AEAF8` / black) |\n| `app_icon.png`, `circle_app_icon.png`, `holder*.png` | bitmaps, not icons |\n\nIf a state needs that colour with a shared symbol, pass it explicitly — e.g. `ActionButton`'s\n`iconColor = Color(0xFF00A0CB)` for the downloaded state.\n\n**Verify the symbol name exists** before assuming a mapping, against\n`google/material-design-icons` → `variablefont/MaterialSymbolsRounded[FILL,GRAD,opsz,wght].codepoints`.\nLegacy names such as `favorite_border`, `add_circle_outline` and `thumb_up_alt` *do* still exist;\n`person_add_alt_1` does not (use `person_add`).\n\n**Glyphs that spill past Material's 20×20 live area are fitted into it (2026-10-18).** Material's\nicon grid keeps a glyph inside the centred 20×20 square of the 24 box, with 2dp of padding, and\nbalances the glyphs inside it by keyline shapes of about equal area. 24 of our 111 Symbols spill\ninto that padding anyway: `Lyrics`' note reaches the box edge, `ThumbsUpDown` spans the\nfull width. At one size those read bigger than everything beside them. `buildSimpIcon()` measures\nhow far each glyph reaches from the centre along either axis (walking its path nodes, 16 samples\nper curve) and shrinks one that passes 10 of 24 about the centre until it fits: `Lyrics` and\n`ThumbsUpDown` ×0.83, the other 21 ×0.86–0.996. A glyph inside the square is left exactly as Google\ndrew it, nothing is enlarged, and anything it cannot measure (an arc, a group, a fit tighter than\n×0.6) keeps the glyph as generated and logs under `SimpIcons`. `SimpIconsFitTest` (jvmTest) fails if\na file skips `buildSimpIcon()`, an icon outside `KEEP_AS_DRAWN` ends up outside the square, or one in\nit gets rebuilt.\n- `Groups` is named in `KEEP_AS_DRAWN` (in `SimpIcons.kt`) and keeps Google's drawing (2026-10-20).\n  It spills only sideways, so fitting its 24 width also cut its height, which was never too tall,\n  from 12 to 10 of 24, and it read shorter than every icon beside it. Five more spill only sideways\n  and lose a little height to the fit (`PeopleAlt`, `PersonAdd`, `Insights` 9%, `AutoGraph` 6%,\n  `AccessAlarm` 2%); they still end 14.5 of 24 tall or more, like Symbols Google draws at that\n  height, so they stay fitted (owner's call).\n- Do not fit glyphs into a circle instead. That was tried first: a circle judges a glyph by its one\n  farthest point, so `Lyrics` shrank to ×0.72 to pull its note's tip inside and then read far\n  smaller than `QueueMusic` beside it.\n\n**A small round button cuts a glyph that fills the live area.** `IconButton` clips its content to\nits `shape` (a circle by default), and a button sized 24dp makes that a 24dp circle, 12dp from the\ncentre. A glyph that fills the 20×20 square reaches 14.1 at its corners — `comment` reaches 13.3 —\nso its corners are cut while round or open glyphs beside it are not. The icon file is not the\nproblem; the button is. Check the farthest glyph point before putting an icon in one: Classic's\nComments button clips to 28dp with `requiredSize(28.dp)` inside the same 24dp slot.\n\n**Icons in one row take one size.** Tuning each glyph by eye is what made the Apple Music row read\nbig-small (23 and 24dp beside 22dp dock buttons, the title-row star 32dp beside 24dp glyphs). Only a\ndeliberate hierarchy, such as a Play button over Previous/Next, gets a different size.\n\n**The response is gzipped** even when the request asks for `identity` — `curl --compressed`, or\ndecompress by magic bytes (`\\x1f\\x8b`) when fetching from a script.\n",".claude/skills/kmp/SKILL.md":"---\nname: kotlin-multiplatform\ndescription: |\n  Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific,\n  source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets\n  (Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues.\n  Triggers on: abstraction decisions (\"should I share this?\"), source set placement questions, expect/actual creation,\n  build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.\n---\n\n# Kotlin Multiplatform: Platform Abstraction Decisions\n\nExpert guidance for KMP architecture in Amethyst - deciding what to share vs keep platform-specific.\n\n## When to Use This Skill\n\nMaking platform abstraction decisions:\n- \"Should I create expect/actual or keep Android-only?\"\n- \"Can I share this ViewModel logic?\"\n- \"Where does this crypto/JSON/network implementation belong?\"\n- \"This uses Android Context - can it be abstracted?\"\n- \"Is this code in the wrong module?\"\n- Preparing for iOS/web/wasm targets\n- Detecting incorrect placements\n\n## Abstraction Decision Tree\n\n**Central question:** \"Should this code be reused across platforms?\"\n\nFollow this decision path (< 1 minute):\n\n```\nQ: Is it used by 2+ platforms?\n├─ NO  → Keep platform-specific\n│         Example: Android-only permission handling\n│\n└─ YES → Continue ↓\n\nQ: Is it pure Kotlin (no platform APIs)?\n├─ YES → commonMain\n│         Example: Nostr event parsing, business rules\n│\n└─ NO  → Continue ↓\n\nQ: Does it vary by platform or by JVM vs non-JVM?\n├─ By platform (Android ≠ iOS ≠ Desktop)\n│  → expect/actual\n│  Example: Secp256k1Instance (uses different security APIs)\n│\n├─ By JVM (Android = Desktop ≠ iOS/web)\n│  → jvmAndroid\n│  Example: Jackson JSON parsing (JVM library)\n│\n└─ Complex/UI-related\n   → Keep platform-specific\n   Example: Navigation (Activity vs Window too different)\n\nFinal check:\nQ: Maintenance cost of abstraction < duplication cost?\n├─ YES → Proceed with abstraction\n└─ NO  → Duplicate (simpler)\n```\n\n### Real Examples from Codebase\n\n**Crypto → expect/actual:**\n```kotlin\n// commonMain - expect declaration\nexpect object Secp256k1Instance {\n    fun signSchnorr(data: ByteArray, privKey: ByteArray): ByteArray\n}\n\n// androidMain - uses Android Keystore\n// jvmMain - uses Desktop JVM crypto\n// iosMain - uses iOS Security framework\n```\n**Why:** Each platform has different security APIs.\n\n**JSON parsing → jvmAndroid:**\n```kotlin\n// quartz/build.gradle.kts\nval jvmAndroid = create(\"jvmAndroid\") {\n    api(libs.jackson.module.kotlin)\n}\n```\n**Why:** Jackson is JVM-only, works on Android + Desktop, not iOS/web.\n\n**Navigation → platform-specific:**\n- Android: `MainActivity` (Activity + Compose Navigation)\n- Desktop: `Window` + sidebar + MenuBar\n**Why:** UI paradigms fundamentally different.\n\n## Mental Model: Source Sets as Dependency Graph\n\nThink of source sets as a dependency graph, not folders.\n\n```\n┌─────────────────────────────────────────────┐\n│ commonMain = Contract (pure Kotlin)         │\n│ - Business logic, protocol, data models     │\n│ - No platform APIs                          │\n└────────────┬────────────────────────────────┘\n             │\n             ├──────────────────────┬────────────────────\n             │                      │\n             ▼                      ▼\n   ┌───────────────────┐  ┌──────────────────┐\n   │ jvmAndroid        │  │ iosMain          │\n   │ JVM libs shared   │  │ iOS common       │\n   │ - Jackson         │  │                  │\n   │ - OkHttp          │  └────┬─────────────┘\n   └───┬───────────┬───┘       │\n       │           │            ├─→ iosX64Main\n       ▼           ▼            ├─→ iosArm64Main\n  ┌─────────┐ ┌──────────┐     └─→ iosSimulatorArm64Main\n  │android  │ │jvmMain   │\n  │Main     │ │(Desktop) │\n  └─────────┘ └──────────┘\n\nFuture: jsMain, wasmMain\n```\n\n**Key insight:** jvmAndroid is NOT a platform - it's a shared JVM layer.\n\n## The jvmAndroid Pattern\n\n**Unique to Amethyst.** Shares JVM libraries between Android + Desktop.\n\n### When to Use jvmAndroid\n\nUse jvmAndroid when:\n- ✅ JVM-specific libraries (Jackson, OkHttp, url-detector)\n- ✅ Android implementation = Desktop implementation (same JVM)\n- ✅ Library doesn't work on iOS/web\n\nDo NOT use jvmAndroid for:\n- ❌ Pure Kotlin code (use commonMain)\n- ❌ Platform-specific APIs (use androidMain/jvmMain)\n- ❌ Code that should work on all platforms\n\n### Example from quartz/build.gradle.kts\n\n```kotlin\n// Must be defined BEFORE androidMain and jvmMain\nval jvmAndroid = create(\"jvmAndroid\") {\n    dependsOn(commonMain.get())\n\n    dependencies {\n        api(libs.jackson.module.kotlin)  // JSON parsing - JVM only\n        api(libs.url.detector)            // URL extraction - JVM only\n        implementation(libs.okhttp)       // HTTP client - JVM only\n    }\n}\n\n// Both depend on jvmAndroid\njvmMain { dependsOn(jvmAndroid) }\nandroidMain { dependsOn(jvmAndroid) }\n```\n\n**Why Jackson in jvmAndroid, not commonMain?**\n- Jackson is JVM-specific library\n- Works on Android (runs on JVM)\n- Works on Desktop (runs on JVM)\n- Does NOT work on iOS (not JVM) or web (not JVM)\n\n**Web/wasm consideration:** For future web support, consider migrating from Jackson → kotlinx.serialization (see Target-Specific Guidance).\n\n## What to Abstract vs Keep Platform-Specific\n\nQuick decision guidelines based on codebase patterns:\n\n### Always Abstract\n- **Crypto** (Secp256k1, encryption, signing)\n- **Core protocol logic** (Nostr events, NIPs)\n- **Why:** Needed everywhere, platform security APIs vary\n\n### Often Abstract\n- **I/O operations** (file reading, caching)\n- **Logging** (platform logging systems differ)\n- **Serialization** (if using kotlinx.serialization)\n- **Why:** Commonly reused, platform implementations available\n\n### Sometimes Abstract\n- **Business logic:** YES - state machines, data processing\n- **ViewModels:** YES - state + business logic shareable (StateFlow/SharedFlow)\n- **Screen layouts:** NO - platform-native (Window vs Activity)\n- **Why:** ViewModels contain platform-agnostic state; Screens render differently per platform\n\n### Rarely Abstract\n- **Complex UI components** (composables with heavy platform dependencies)\n- **Why:** Platform paradigms can differ significantly\n\n### Never Abstract\n- **Navigation** (Activity vs Window fundamentally different)\n- **Permissions** (Android vs iOS APIs incompatible)\n- **Platform UX patterns**\n- **Why:** Too platform-specific, abstraction creates leaky APIs\n\n### Evidence from shared-ui-analysis.md\n\n| Component | Shared? | Rationale |\n|-----------|---------|-----------|\n| PubKeyFormatter, ZapFormatter | ✅ YES | Pure Kotlin, no platform APIs |\n| TimeAgoFormatter | ⚠️ ABSTRACTED | Needs StringProvider for localized strings |\n| ViewModels (state + logic) | ✅ YES | StateFlow/SharedFlow platform-agnostic, Compose Multiplatform lifecycle compatible |\n| Screen layouts (Scaffold, nav) | ❌ NO | Window vs Activity, sidebar vs bottom nav fundamentally different |\n| Image loading (Coil) | ⚠️ ABSTRACTED | Coil 3.x supports KMP, needs expect/actual wrapper |\n\n## expect/actual Mechanics\n\n**When to use:** Code needed by 2+ platforms, varies by platform.\n\n### Pattern Categories from Codebase\n\n**Objects (singletons):**\n```kotlin\n// 24 expect declarations found, common pattern:\nexpect object Secp256k1Instance { ... }\nexpect object Log { ... }\nexpect object LibSodiumInstance { ... }\n```\n\n**Classes (instantiable):**\n```kotlin\nexpect class AESCBC { ... }\nexpect class DigestInstance { ... }\n```\n\n**Functions (utilities):**\n```kotlin\nexpect fun platform(): String\nexpect fun currentTimeSeconds(): Long\n```\n\n**See** [references/expect-actual-catalog.md](references/expect-actual-catalog.md) for complete catalog with rationale.\n\n## Target-Specific Guidance\n\n### Android, JVM (Desktop), iOS - Current Primary Targets\n\n**Status:** Mature patterns, stable APIs\n\n**Android (androidMain):**\n- Uses Android framework (Activity, Context, etc.)\n- secp256k1-kmp-jni-android for crypto\n- AndroidX libraries\n\n**Desktop JVM (jvmMain):**\n- Uses Compose Desktop (Window, MenuBar, etc.)\n- secp256k1-kmp-jni-jvm for crypto\n- Pure JVM libraries\n\n**iOS (iosMain):**\n- Active development, framework configured\n- Architecture targets: iosX64Main, iosArm64Main, iosSimulatorArm64Main\n- Platform APIs via platform.posix, Security framework\n\n### Web, wasm - Future Targets\n\n**Status:** Not yet implemented, consider for future-proofing\n\n**Constraints to know:**\n- ❌ No platform.posix (file I/O different)\n- ❌ No JVM libraries (Jackson, OkHttp won't work)\n- ❌ Different async model (JS event loop vs threads)\n\n**Future-proofing tips:**\n1. Prefer pure Kotlin in commonMain\n2. Use kotlinx.* libraries:\n   - kotlinx.serialization instead of Jackson\n   - ktor instead of OkHttp (ktor supports web)\n   - kotlinx.datetime instead of custom date handling\n3. Avoid platform.posix for file operations\n4. Test abstractions work without JVM assumptions\n\n**Example migration path:**\n```kotlin\n// Current: jvmAndroid (JVM-only)\napi(libs.jackson.module.kotlin)\n\n// Future: commonMain (all platforms)\napi(libs.kotlinx.serialization.json)\n```\n\n## Integration: When to Invoke Other Skills\n\n### Invoke gradle-expert\n\nTrigger gradle-expert skill when encountering:\n- Dependency conflicts (e.g., secp256k1-android vs secp256k1-jvm version mismatch)\n- Build errors related to source sets\n- Version catalog issues (libs.versions.toml)\n- \"Duplicate class\" errors\n- Performance/build time issues\n\n**Example trigger:**\n```\nError: Duplicate class found: fr.acinq.secp256k1.Secp256k1\n```\n→ Invoke gradle-expert for dependency conflict resolution.\n\n### Flags to Raise\n\n**Platform code in commonMain:**\n```kotlin\n// ❌ INCORRECT - Android API in commonMain\nexpect fun getContext(): Context  // Context is Android-only!\n```\n→ Flag: \"Android API in commonMain won't compile on other platforms\"\n\n**Duplicated business logic:**\n```kotlin\n// ❌ INCORRECT - Same logic in both\n// androidMain/.../CryptoUtils.kt\nfun validateSignature(...) { ... }\n\n// jvmMain/.../CryptoUtils.kt\nfun validateSignature(...) { ... }  // Duplicated!\n```\n→ Flag: \"Business logic duplicated, should be in commonMain or expect/actual\"\n\n**Reinventing wheel - suggest KMP alternatives:**\n- Custom date/time → kotlinx.datetime\n- OkHttp → ktor (supports web)\n- Jackson → kotlinx.serialization\n- Custom UUID → kotlinx.uuid (when stable)\n\n## Common Pitfalls\n\n### 1. Over-Abstraction\n**Problem:** Creating expect/actual for UI components\n```kotlin\n// ❌ BAD\nexpect fun NavigationComponent(...)\n```\n**Why:** Navigation paradigms too different (Activity vs Window)\n**Fix:** Keep platform-specific, accept duplication\n\n### 2. Under-Sharing\n**Problem:** Duplicating business logic across platforms\n```kotlin\n// ❌ BAD - duplicated in androidMain and jvmMain\nfun parseNostrEvent(json: String): Event { ... }\n```\n**Why:** Bug fixes need to be applied twice, tests duplicated\n**Fix:** Move to commonMain (pure Kotlin) or create expect/actual\n\n### 3. Leaky Abstractions\n**Problem:** Platform code in commonMain\n```kotlin\n// commonMain - ❌ BAD\nimport android.content.Context  // Won't compile on iOS!\n```\n**Fix:** Use expect/actual or dependency injection\n\n### 4. Premature Abstraction\n**Problem:** Creating expect/actual before second platform needs it\n```kotlin\n// ❌ BAD - only used on Android currently\nexpect fun showNotification(...)\n```\n**Why:** Wrong abstraction boundaries, wasted effort\n**Fix:** Wait until iOS actually needs it, then abstract\n\n### 5. Wrong Source Set\n**Problem:** JVM libraries in commonMain\n```kotlin\n// commonMain - ❌ BAD\nimport com.fasterxml.jackson.databind.ObjectMapper\n```\n**Why:** Jackson won't compile on iOS/web\n**Fix:** Move to jvmAndroid or migrate to kotlinx.serialization\n\n## Quick Reference\n\n| Code Type | Recommended Location | Reason |\n|-----------|---------------------|--------|\n| Pure Kotlin business logic | commonMain | Works everywhere |\n| Nostr protocol, NIPs | commonMain | Core logic, no platform APIs |\n| JVM libs (Jackson, OkHttp) | jvmAndroid | Android + Desktop only |\n| Crypto (varies by platform) | expect in commonMain, actual in platforms | Different security APIs per platform |\n| I/O, logging | expect in commonMain, actual in platforms | Platform implementations differ |\n| State (business logic) | commonMain or commons/jvmAndroid | Reusable StateFlow patterns |\n| **ViewModels** | **commons/commonMain/viewmodels/** | **StateFlow/SharedFlow + logic shareable, Compose MP lifecycle compatible** |\n| UI formatters (pure) | commons/commonMain | Reusable, no dependencies |\n| UI components (simple) | commons/commonMain | Cards, buttons, dialogs |\n| **Screen layouts** | **Platform-specific** | **Window vs Activity, sidebar vs bottom nav** |\n| Navigation | Platform-specific only | Activity vs Window too different |\n| Permissions | Platform-specific only | APIs incompatible |\n| Platform UX (menus, etc.) | Platform-specific only | Native feel required |\n\n## See Also\n\n- [references/abstraction-examples.md](references/abstraction-examples.md) - Good/bad abstraction examples with rationale\n- [references/source-set-hierarchy.md](references/source-set-hierarchy.md) - Visual hierarchy with Amethyst examples\n- [references/expect-actual-catalog.md](references/expect-actual-catalog.md) - All 24 expect/actual pairs with \"why abstracted\"\n- [references/target-compatibility.md](references/target-compatibility.md) - Platform constraints and future-proofing\n\n## Scripts\n\n- `scripts/validate-kmp-structure.sh` - Detect incorrect placements, validate source sets\n- `scripts/suggest-kmp-dependency.sh` - Suggest KMP library alternatives (ktor, kotlinx.serialization, etc.)\n",".claude/skills/compose-ui/SKILL.md":"---\nname: compose-ui\ndescription: Best practices for building UI with Jetpack Compose, focusing on state hoisting, detailed performance optimizations, and theming. Use this when writing or refactoring Composable functions.\n---\n\n# Jetpack Compose Best Practices\n\n## Instructions\n\nFollow these guidelines to create performant, reusable, and testable Composables.\n\n### 1. State Hoisting (Unidirectional Data Flow)\nMake Composables **stateless** whenever possible by moving state to the caller.\n\n*   **Pattern**: Function signature should usually look like:\n    ```kotlin\n    @Composable\n    fun MyComponent(\n        value: String,              // State flows down\n        onValueChange: (String) -> Unit, // Events flow up\n        modifier: Modifier = Modifier // Standard modifier parameter\n    )\n    ```\n*   **Benefit**: Decouples the UI from simple state storage, making it easier to preview and test.\n*   **ViewModel Integration**: The screen-level Composable retrieves state from the ViewModel (`viewModel.uiState.collectAsStateWithLifecycle()`) and passes it down.\n\n### 2. Modifiers\n*   **Default Parameter**: Always provide a `modifier: Modifier = Modifier` as the first optional parameter.\n*   **Application**: Apply this `modifier` to the *root* layout element of your Composable.\n*   **Ordering matters**: `padding().clickable()` is different from `clickable().padding()`. Generally apply layout-affecting modifiers (like padding) *after* click listeners if you want the padding to be clickable.\n\n### 3. Performance Optimization\n*   **`remember`**: Use `remember { ... }` to cache expensive calculations across recompositions.\n*   **`derivedStateOf`**: Use `derivedStateOf { ... }` when a state changes frequently (like scroll position) but the UI only needs to react to a threshold or summary (e.g., show \"Jump to Top\" button). This prevents unnecessary recompositions.\n    ```kotlin\n    val showButton by remember {\n        derivedStateOf { listState.firstVisibleItemIndex > 0 }\n    }\n    ```\n*   **Lambda Stability**: Prefer method references (e.g., `viewModel::onEvent`) or remembered lambdas to prevent unstable types from triggering recomposition of children.\n\n### 4. Theming and Resources\n*   Use `MaterialTheme.colorScheme` and `MaterialTheme.typography` instead of hardcoded colors or text styles.\n*   Organize simple UI components into specific files (e.g., `DesignSystem.kt` or `Components.kt`) if they are shared across features.\n\n### 5. Previews\n*   Create a private preview function for every public Composable.\n*   Use `@Preview(showBackground = true)` and include Light/Dark mode previews if applicable.\n*   Pass dummy data (static) to the stateless Composable for the preview.\n",".claude/skills/gradle-build-performance/SKILL.md":"---\nname: gradle-build-performance\ndescription: Debug and optimize Android/Gradle build performance. Use when builds are slow, investigating CI/CD performance, analyzing build scans, or identifying compilation bottlenecks.\n---\n\n# Gradle Build Performance\n\n## When to Use\n\n- Build times are slow (clean or incremental)\n- Investigating build performance regressions\n- Analyzing Gradle Build Scans\n- Identifying configuration vs execution bottlenecks\n- Optimizing CI/CD build times\n- Enabling Gradle Configuration Cache\n- Reducing unnecessary recompilation\n- Debugging kapt/KSP annotation processing\n\n## Example Prompts\n\n- \"My builds are slow, how can I speed them up?\"\n- \"How do I analyze a Gradle build scan?\"\n- \"Why is configuration taking so long?\"\n- \"Why does my project always recompile everything?\"\n- \"How do I enable configuration cache?\"\n- \"Why is kapt so slow?\"\n\n---\n\n## Workflow\n\n1. **Measure Baseline** — Clean build + incremental build times\n2. **Generate Build Scan** — `./gradlew assembleDebug --scan`\n3. **Identify Phase** — Configuration? Execution? Dependency resolution?\n4. **Apply ONE optimization** — Don't batch changes\n5. **Measure Improvement** — Compare against baseline\n6. **Verify in Build Scan** — Visual confirmation\n\n---\n\n## Quick Diagnostics\n\n### Generate Build Scan\n\n```bash\n./gradlew assembleDebug --scan\n```\n\n### Profile Build Locally\n\n```bash\n./gradlew assembleDebug --profile\n# Opens report in build/reports/profile/\n```\n\n### Build Timing Summary\n\n```bash\n./gradlew assembleDebug --info | grep -E \"^\\:.*\"\n# Or view in Android Studio: Build > Analyze APK Build\n```\n\n---\n\n## Build Phases\n\n| Phase | What Happens | Common Issues |\n|-------|--------------|---------------|\n| **Initialization** | `settings.gradle.kts` evaluated | Too many `include()` statements |\n| **Configuration** | All `build.gradle.kts` files evaluated | Expensive plugins, eager task creation |\n| **Execution** | Tasks run based on inputs/outputs | Cache misses, non-incremental tasks |\n\n### Identify the Bottleneck\n\n```\nBuild scan → Performance → Build timeline\n```\n\n- **Long configuration phase**: Focus on plugin and buildscript optimization\n- **Long execution phase**: Focus on task caching and parallelization\n- **Dependency resolution slow**: Focus on repository configuration\n\n---\n\n## 12 Optimization Patterns\n\n### 1. Enable Configuration Cache\n\nCaches configuration phase across builds (AGP 8.0+):\n\n```properties\n# gradle.properties\norg.gradle.configuration-cache=true\norg.gradle.configuration-cache.problems=warn\n```\n\n### 2. Enable Build Cache\n\nReuses task outputs across builds and machines:\n\n```properties\n# gradle.properties\norg.gradle.caching=true\n```\n\n### 3. Enable Parallel Execution\n\nBuild independent modules simultaneously:\n\n```properties\n# gradle.properties\norg.gradle.parallel=true\n```\n\n### 4. Increase JVM Heap\n\nAllocate more memory for large projects:\n\n```properties\n# gradle.properties\norg.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC\n```\n\n### 5. Use Non-Transitive R Classes\n\nReduces R class size and compilation (AGP 8.0+ default):\n\n```properties\n# gradle.properties\nandroid.nonTransitiveRClass=true\n```\n\n### 6. Migrate kapt to KSP\n\nKSP is 2x faster than kapt for Kotlin:\n\n```kotlin\n// Before (slow)\nkapt(\"com.google.dagger:hilt-compiler:2.51.1\")\n\n// After (fast)\nksp(\"com.google.dagger:hilt-compiler:2.51.1\")\n```\n\n### 7. Avoid Dynamic Dependencies\n\nPin dependency versions:\n\n```kotlin\n// BAD: Forces resolution every build\nimplementation(\"com.example:lib:+\")\nimplementation(\"com.example:lib:1.0.+\")\n\n// GOOD: Fixed version\nimplementation(\"com.example:lib:1.2.3\")\n```\n\n### 8. Optimize Repository Order\n\nPut most-used repositories first:\n\n```kotlin\n// settings.gradle.kts\ndependencyResolutionManagement {\n    repositories {\n        google()      // First: Android dependencies\n        mavenCentral() // Second: Most libraries\n        // Third-party repos last\n    }\n}\n```\n\n### 9. Use includeBuild for Local Modules\n\nComposite builds are faster than `project()` for large monorepos:\n\n```kotlin\n// settings.gradle.kts\nincludeBuild(\"shared-library\") {\n    dependencySubstitution {\n        substitute(module(\"com.example:shared\")).using(project(\":\"))\n    }\n}\n```\n\n### 10. Enable Incremental Annotation Processing\n\n```properties\n# gradle.properties\nkapt.incremental.apt=true\nkapt.use.worker.api=true\n```\n\n### 11. Avoid Configuration-Time I/O\n\nDon't read files or make network calls during configuration:\n\n```kotlin\n// BAD: Runs during configuration\nval version = file(\"version.txt\").readText()\n\n// GOOD: Defer to execution\nval version = providers.fileContents(file(\"version.txt\")).asText\n```\n\n### 12. Use Lazy Task Configuration\n\nAvoid `create()`, use `register()`:\n\n```kotlin\n// BAD: Eagerly configured\ntasks.create(\"myTask\") { ... }\n\n// GOOD: Lazily configured\ntasks.register(\"myTask\") { ... }\n```\n\n---\n\n## Common Bottleneck Analysis\n\n### Slow Configuration Phase\n\n**Symptoms**: Build scan shows long \"Configuring build\" time\n\n**Causes & Fixes**:\n| Cause | Fix |\n|-------|-----|\n| Eager task creation | Use `tasks.register()` instead of `tasks.create()` |\n| buildSrc with many dependencies | Migrate to Convention Plugins with `includeBuild` |\n| File I/O in build scripts | Use `providers.fileContents()` |\n| Network calls in plugins | Cache results or use offline mode |\n\n### Slow Compilation\n\n**Symptoms**: `:app:compileDebugKotlin` takes too long\n\n**Causes & Fixes**:\n| Cause | Fix |\n|-------|-----|\n| Non-incremental changes | Avoid `build.gradle.kts` changes that invalidate cache |\n| Large modules | Break into smaller feature modules |\n| Excessive kapt usage | Migrate to KSP |\n| Kotlin compiler memory | Increase `kotlin.daemon.jvmargs` |\n\n### Cache Misses\n\n**Symptoms**: Tasks always rerun despite no changes\n\n**Causes & Fixes**:\n| Cause | Fix |\n|-------|-----|\n| Unstable task inputs | Use `@PathSensitive`, `@NormalizeLineEndings` |\n| Absolute paths in outputs | Use relative paths |\n| Missing `@CacheableTask` | Add annotation to custom tasks |\n| Different JDK versions | Standardize JDK across environments |\n\n---\n\n## CI/CD Optimizations\n\n### Remote Build Cache\n\n```kotlin\n// settings.gradle.kts\nbuildCache {\n    local { isEnabled = true }\n    remote<HttpBuildCache> {\n        url = uri(\"https://cache.example.com/\")\n        isPush = System.getenv(\"CI\") == \"true\"\n        credentials {\n            username = System.getenv(\"CACHE_USER\")\n            password = System.getenv(\"CACHE_PASS\")\n        }\n    }\n}\n```\n\n### Gradle Enterprise / Develocity\n\nFor advanced build analytics:\n\n```kotlin\n// settings.gradle.kts\nplugins {\n    id(\"com.gradle.develocity\") version \"3.17\"\n}\n\ndevelocity {\n    buildScan {\n        termsOfUseUrl.set(\"https://gradle.com/help/legal-terms-of-use\")\n        termsOfUseAgree.set(\"yes\")\n        publishing.onlyIf { System.getenv(\"CI\") != null }\n    }\n}\n```\n\n### Skip Unnecessary Tasks in CI\n\n```bash\n# Skip tests for UI-only changes\n./gradlew assembleDebug -x test -x lint\n\n# Only run affected module tests\n./gradlew :feature:login:test\n```\n\n---\n\n## Android Studio Settings\n\n### File → Settings → Build → Gradle\n\n- **Gradle JDK**: Match your project's JDK\n- **Build and run using**: Gradle (not IntelliJ)\n- **Run tests using**: Gradle\n\n### File → Settings → Build → Compiler\n\n- **Compile independent modules in parallel**: ✅ Enabled\n- **Configure on demand**: ❌ Disabled (deprecated)\n\n---\n\n## Verification Checklist\n\nAfter optimizations, verify:\n\n- [ ] Configuration cache enabled and working\n- [ ] Build cache hit rate > 80% (check build scan)\n- [ ] No dynamic dependency versions\n- [ ] KSP used instead of kapt where possible\n- [ ] Parallel execution enabled\n- [ ] JVM memory tuned appropriately\n- [ ] CI remote cache configured\n- [ ] No configuration-time I/O\n\n---\n\n## References\n\n- [Optimize Build Speed](https://developer.android.com/build/optimize-your-build)\n- [Gradle Configuration Cache](https://docs.gradle.org/current/userguide/configuration_cache.html)\n- [Gradle Build Cache](https://docs.gradle.org/current/userguide/build_cache.html)\n- [Migrate from kapt to KSP](https://developer.android.com/build/migrate-to-ksp)\n- [Gradle Build Scans](https://scans.gradle.com/)\n",".claude/skills/compose-performance-audit/SKILL.md":"---\nname: compose-performance-audit\ndescription: Audit and improve Jetpack Compose runtime performance from code review and architecture. Use when asked to diagnose slow rendering, janky scrolling, excessive recompositions, or performance issues in Compose UI.\n---\n\n# Compose Performance Audit\n\n## Overview\n\nAudit Jetpack Compose view performance end-to-end, from instrumentation and baselining to root-cause analysis and concrete remediation steps.\n\n## Workflow Decision Tree\n\n- If the user provides code, start with \"Code-First Review.\"\n- If the user only describes symptoms, ask for minimal code/context, then do \"Code-First Review.\"\n- If code review is inconclusive, go to \"Guide the User to Profile\" and ask for Layout Inspector output or Perfetto traces.\n\n## 1. Code-First Review\n\nCollect:\n- Target Composable code.\n- Data flow: state, remember, derived state, ViewModel connections.\n- Symptoms and reproduction steps.\n\nFocus on:\n- **Recomposition storms** from unstable parameters or broad state changes.\n- **Unstable keys** in `LazyColumn`/`LazyRow` (`key` churn, missing keys).\n- **Heavy work in composition** (formatting, sorting, filtering, object allocation).\n- **Unnecessary recompositions** (missing `remember`, unstable classes, lambdas).\n- **Large images** without proper sizing or async loading.\n- **Layout thrash** (deep nesting, intrinsic measurements, `SubcomposeLayout` misuse).\n\nProvide:\n- Likely root causes with code references.\n- Suggested fixes and refactors.\n- If needed, a minimal repro or instrumentation suggestion.\n\n## 2. Guide the User to Profile\n\nExplain how to collect data:\n- Use **Layout Inspector** in Android Studio to see recomposition counts.\n- Enable **Recomposition Highlights** in Compose tooling.\n- Use **Perfetto** or **System Trace** for frame timing analysis.\n- Check **Macrobenchmark** results for startup/scroll metrics.\n\nAsk for:\n- Layout Inspector screenshot showing recomposition counts.\n- Perfetto trace or System Trace export.\n- Device/OS/build configuration (debug vs release).\n\n> **Important**: Ensure profiling is done on a **release build** with R8 enabled. Debug builds have significant overhead.\n\n## 3. Analyze and Diagnose\n\nPrioritize likely Compose culprits:\n- **Recomposition storms** from unstable parameters or broad state changes.\n- **Unstable keys** in lazy lists (`key` churn, index-based keys).\n- **Heavy work in composition** (formatting, sorting, object allocation).\n- **Missing `remember`** causing recreations on every recomposition.\n- **Large images** without `Modifier.size()` constraints.\n- **Unnecessary state reads** in wrong composition phases.\n\nSummarize findings with evidence from traces/Layout Inspector.\n\n## 4. Remediate\n\nApply targeted fixes:\n- **Stabilize parameters**: Use `@Stable` or `@Immutable` annotations on data classes.\n- **Stabilize keys**: Use stable, unique IDs for `LazyColumn`/`LazyRow` items.\n- **Defer state reads**: Use `derivedStateOf`, lambda-based modifiers, or `Modifier.drawBehind`.\n- **Remember expensive computations**: Wrap in `remember { }` or `remember(key) { }`.\n- **Skip recomposition**: Extract stable composables, use `key()` to control identity.\n- **Async image loading**: Use Coil/Glide with proper sizing constraints.\n- **Reduce layout complexity**: Flatten hierarchies, avoid deep nesting.\n\n## Common Code Smells (and Fixes)\n\n### Unstable lambda captures\n\n```kotlin\n// BAD: New lambda instance every recomposition\nButton(onClick = { viewModel.doSomething(item) }) { ... }\n\n// GOOD: Use remember or method reference\nval onClick = remember(item) { { viewModel.doSomething(item) } }\nButton(onClick = onClick) { ... }\n```\n\n### Expensive work in composition\n\n```kotlin\n// BAD: Sorting on every recomposition\n@Composable\nfun ItemList(items: List<Item>) {\n    val sorted = items.sortedBy { it.name } // Runs every recomposition\n    LazyColumn { items(sorted) { ... } }\n}\n\n// GOOD: Use remember with key\n@Composable\nfun ItemList(items: List<Item>) {\n    val sorted = remember(items) { items.sortedBy { it.name } }\n    LazyColumn { items(sorted) { ... } }\n}\n```\n\n### Missing keys in LazyColumn\n\n```kotlin\n// BAD: Index-based identity (causes recomposition on list changes)\nLazyColumn {\n    items(items) { item -> ItemRow(item) }\n}\n\n// GOOD: Stable key-based identity\nLazyColumn {\n    items(items, key = { it.id }) { item -> ItemRow(item) }\n}\n```\n\n### Unstable data classes\n\n```kotlin\n// BAD: Unstable (contains List, which is not stable)\ndata class UiState(\n    val items: List<Item>,\n    val isLoading: Boolean\n)\n\n// GOOD: Mark as Immutable if truly immutable\n@Immutable\ndata class UiState(\n    val items: ImmutableList<Item>, // kotlinx.collections.immutable\n    val isLoading: Boolean\n)\n```\n\n### Reading state too early\n\n```kotlin\n// BAD: State read during composition (recomposes whole tree)\n@Composable\nfun AnimatedBox(scrollState: ScrollState) {\n    val offset = scrollState.value // Recomposes on every scroll\n    Box(modifier = Modifier.offset(y = offset.dp)) { ... }\n}\n\n// GOOD: Defer state read to layout/draw phase\n@Composable\nfun AnimatedBox(scrollState: ScrollState) {\n    Box(modifier = Modifier.offset {\n        IntOffset(0, scrollState.value) // Read in layout phase\n    }) { ... }\n}\n```\n\n### Object allocation in composition\n\n```kotlin\n// BAD: Creates new Modifier chain every recomposition\nBox(modifier = Modifier.padding(16.dp).background(Color.Red))\n\n// GOOD for dynamic modifiers: Remember the modifier\nval modifier = remember { Modifier.padding(16.dp).background(Color.Red) }\nBox(modifier = modifier)\n```\n\n## Stability Checklist\n\n| Type | Stable by Default? | Fix |\n|------|-------------------|-----|\n| Primitives (`Int`, `String`, `Boolean`) | Yes | N/A |\n| `data class` with stable fields | Yes* | Ensure all fields are stable |\n| `List`, `Map`, `Set` | **No** | Use `ImmutableList` from kotlinx |\n| Classes with `var` properties | **No** | Use `@Stable` if externally stable |\n| Lambdas | **No** | Use `remember { }` |\n\n## 5. Verify\n\nAsk the user to:\n- Re-run Layout Inspector and compare recomposition counts.\n- Run Macrobenchmark and compare frame timing.\n- Test on a real device with release build.\n\nSummarize the delta (recomposition count, frame drops, jank) if provided.\n\n## Outputs\n\nProvide:\n- A short metrics table (before/after if available).\n- Top issues (ordered by impact).\n- Proposed fixes with estimated effort.\n\n## References\n\n- [Jetpack Compose Performance](https://developer.android.com/develop/ui/compose/performance)\n- [Compose Stability Explained](https://developer.android.com/develop/ui/compose/performance/stability)\n- [Debugging Recomposition](https://developer.android.com/develop/ui/compose/tooling/layout-inspector)\n- [Macrobenchmark](https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview)\n",".claude/skills/mobile-android-design/SKILL.md":"---\nname: mobile-android-design\ndescription: Master Material Design 3 and Jetpack Compose patterns for building native Android apps. Use when designing Android interfaces, implementing Compose UI, or following Google's Material Design guidelines.\n---\n\n# Android Mobile Design\n\nMaster Material Design 3 (Material You) and Jetpack Compose to build modern, adaptive Android applications that integrate seamlessly with the Android ecosystem.\n\n## When to Use This Skill\n\n- Designing Android app interfaces following Material Design 3\n- Building Jetpack Compose UI and layouts\n- Implementing Android navigation patterns (Navigation Compose)\n- Creating adaptive layouts for phones, tablets, and foldables\n- Using Material 3 theming with dynamic colors\n- Building accessible Android interfaces\n- Implementing Android-specific gestures and interactions\n- Designing for different screen configurations\n\n## Core Concepts\n\n### 1. Material Design 3 Principles\n\n**Personalization**: Dynamic color adapts UI to user's wallpaper\n**Accessibility**: Tonal palettes ensure sufficient color contrast\n**Large Screens**: Responsive layouts for tablets and foldables\n\n**Material Components:**\n\n- Cards, Buttons, FABs, Chips\n- Navigation (rail, drawer, bottom nav)\n- Text fields, Dialogs, Sheets\n- Lists, Menus, Progress indicators\n\n### 2. Jetpack Compose Layout System\n\n**Column and Row:**\n\n```kotlin\n// Vertical arrangement with alignment\nColumn(\n    modifier = Modifier.padding(16.dp),\n    verticalArrangement = Arrangement.spacedBy(12.dp),\n    horizontalAlignment = Alignment.Start\n) {\n    Text(\n        text = \"Title\",\n        style = MaterialTheme.typography.headlineSmall\n    )\n    Text(\n        text = \"Subtitle\",\n        style = MaterialTheme.typography.bodyMedium,\n        color = MaterialTheme.colorScheme.onSurfaceVariant\n    )\n}\n\n// Horizontal arrangement with weight\nRow(\n    modifier = Modifier.fillMaxWidth(),\n    horizontalArrangement = Arrangement.SpaceBetween,\n    verticalAlignment = Alignment.CenterVertically\n) {\n    Icon(Icons.Default.Star, contentDescription = null)\n    Text(\"Featured\")\n    Spacer(modifier = Modifier.weight(1f))\n    TextButton(onClick = {}) {\n        Text(\"View All\")\n    }\n}\n```\n\n**Lazy Lists and Grids:**\n\n```kotlin\n// Lazy column with sticky headers\nLazyColumn {\n    items.groupBy { it.category }.forEach { (category, categoryItems) ->\n        stickyHeader {\n            Text(\n                text = category,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.surface)\n                    .padding(16.dp),\n                style = MaterialTheme.typography.titleMedium\n            )\n        }\n        items(categoryItems) { item ->\n            ItemRow(item = item)\n        }\n    }\n}\n\n// Adaptive grid\nLazyVerticalGrid(\n    columns = GridCells.Adaptive(minSize = 150.dp),\n    contentPadding = PaddingValues(16.dp),\n    horizontalArrangement = Arrangement.spacedBy(12.dp),\n    verticalArrangement = Arrangement.spacedBy(12.dp)\n) {\n    items(items) { item ->\n        ItemCard(item = item)\n    }\n}\n```\n\n### 3. Navigation Patterns\n\n**Bottom Navigation:**\n\n```kotlin\n@Composable\nfun MainScreen() {\n    val navController = rememberNavController()\n\n    Scaffold(\n        bottomBar = {\n            NavigationBar {\n                val navBackStackEntry by navController.currentBackStackEntryAsState()\n                val currentDestination = navBackStackEntry?.destination\n\n                NavigationDestination.entries.forEach { destination ->\n                    NavigationBarItem(\n                        icon = { Icon(destination.icon, contentDescription = null) },\n                        label = { Text(destination.label) },\n                        selected = currentDestination?.hierarchy?.any {\n                            it.route == destination.route\n                        } == true,\n                        onClick = {\n                            navController.navigate(destination.route) {\n                                popUpTo(navController.graph.findStartDestination().id) {\n                                    saveState = true\n                                }\n                                launchSingleTop = true\n                                restoreState = true\n                            }\n                        }\n                    )\n                }\n            }\n        }\n    ) { innerPadding ->\n        NavHost(\n            navController = navController,\n            startDestination = NavigationDestination.Home.route,\n            modifier = Modifier.padding(innerPadding)\n        ) {\n            composable(NavigationDestination.Home.route) { HomeScreen() }\n            composable(NavigationDestination.Search.route) { SearchScreen() }\n            composable(NavigationDestination.Profile.route) { ProfileScreen() }\n        }\n    }\n}\n```\n\n**Navigation Drawer:**\n\n```kotlin\n@Composable\nfun DrawerNavigation() {\n    val drawerState = rememberDrawerState(DrawerValue.Closed)\n    val scope = rememberCoroutineScope()\n\n    ModalNavigationDrawer(\n        drawerState = drawerState,\n        drawerContent = {\n            ModalDrawerSheet {\n                Spacer(Modifier.height(12.dp))\n                Text(\n                    \"App Name\",\n                    modifier = Modifier.padding(16.dp),\n                    style = MaterialTheme.typography.titleLarge\n                )\n                HorizontalDivider()\n\n                NavigationDrawerItem(\n                    icon = { Icon(Icons.Default.Home, null) },\n                    label = { Text(\"Home\") },\n                    selected = true,\n                    onClick = { scope.launch { drawerState.close() } }\n                )\n                NavigationDrawerItem(\n                    icon = { Icon(Icons.Default.Settings, null) },\n                    label = { Text(\"Settings\") },\n                    selected = false,\n                    onClick = { }\n                )\n            }\n        }\n    ) {\n        Scaffold(\n            topBar = {\n                TopAppBar(\n                    title = { Text(\"Home\") },\n                    navigationIcon = {\n                        IconButton(onClick = { scope.launch { drawerState.open() } }) {\n                            Icon(Icons.Default.Menu, contentDescription = \"Menu\")\n                        }\n                    }\n                )\n            }\n        ) { innerPadding ->\n            Content(modifier = Modifier.padding(innerPadding))\n        }\n    }\n}\n```\n\n### 4. Material 3 Theming\n\n**Color Scheme:**\n\n```kotlin\n// Dynamic color (Android 12+)\nval dynamicColorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n    val context = LocalContext.current\n    if (darkTheme) dynamicDarkColorScheme(context)\n    else dynamicLightColorScheme(context)\n} else {\n    if (darkTheme) DarkColorScheme else LightColorScheme\n}\n\n// Custom color scheme\nprivate val LightColorScheme = lightColorScheme(\n    primary = Color(0xFF6750A4),\n    onPrimary = Color.White,\n    primaryContainer = Color(0xFFEADDFF),\n    onPrimaryContainer = Color(0xFF21005D),\n    secondary = Color(0xFF625B71),\n    onSecondary = Color.White,\n    tertiary = Color(0xFF7D5260),\n    onTertiary = Color.White,\n    surface = Color(0xFFFFFBFE),\n    onSurface = Color(0xFF1C1B1F),\n)\n```\n\n**Typography:**\n\n```kotlin\nval AppTypography = Typography(\n    displayLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 57.sp,\n        lineHeight = 64.sp\n    ),\n    headlineMedium = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 28.sp,\n        lineHeight = 36.sp\n    ),\n    titleLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 22.sp,\n        lineHeight = 28.sp\n    ),\n    bodyLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 16.sp,\n        lineHeight = 24.sp\n    ),\n    labelMedium = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Medium,\n        fontSize = 12.sp,\n        lineHeight = 16.sp\n    )\n)\n```\n\n### 5. Component Examples\n\n**Cards:**\n\n```kotlin\n@Composable\nfun FeatureCard(\n    title: String,\n    description: String,\n    imageUrl: String,\n    onClick: () -> Unit\n) {\n    Card(\n        onClick = onClick,\n        modifier = Modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(16.dp),\n        colors = CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surfaceVariant\n        )\n    ) {\n        Column {\n            AsyncImage(\n                model = imageUrl,\n                contentDescription = null,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .height(180.dp),\n                contentScale = ContentScale.Crop\n            )\n            Column(modifier = Modifier.padding(16.dp)) {\n                Text(\n                    text = title,\n                    style = MaterialTheme.typography.titleMedium\n                )\n                Spacer(modifier = Modifier.height(8.dp))\n                Text(\n                    text = description,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            }\n        }\n    }\n}\n```\n\n**Buttons:**\n\n```kotlin\n// Filled button (primary action)\nButton(onClick = { }) {\n    Text(\"Continue\")\n}\n\n// Filled tonal button (secondary action)\nFilledTonalButton(onClick = { }) {\n    Icon(Icons.Default.Add, null)\n    Spacer(Modifier.width(8.dp))\n    Text(\"Add Item\")\n}\n\n// Outlined button\nOutlinedButton(onClick = { }) {\n    Text(\"Cancel\")\n}\n\n// Text button\nTextButton(onClick = { }) {\n    Text(\"Learn More\")\n}\n\n// FAB\nFloatingActionButton(\n    onClick = { },\n    containerColor = MaterialTheme.colorScheme.primaryContainer,\n    contentColor = MaterialTheme.colorScheme.onPrimaryContainer\n) {\n    Icon(Icons.Default.Add, contentDescription = \"Add\")\n}\n```\n\n## Quick Start Component\n\n```kotlin\n@Composable\nfun ItemListCard(\n    item: Item,\n    onItemClick: () -> Unit,\n    modifier: Modifier = Modifier\n) {\n    Card(\n        onClick = onItemClick,\n        modifier = modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(12.dp)\n    ) {\n        Row(\n            modifier = Modifier\n                .padding(16.dp)\n                .fillMaxWidth(),\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            Box(\n                modifier = Modifier\n                    .size(48.dp)\n                    .clip(CircleShape)\n                    .background(MaterialTheme.colorScheme.primaryContainer),\n                contentAlignment = Alignment.Center\n            ) {\n                Icon(\n                    imageVector = Icons.Default.Star,\n                    contentDescription = null,\n                    tint = MaterialTheme.colorScheme.onPrimaryContainer\n                )\n            }\n\n            Spacer(modifier = Modifier.width(16.dp))\n\n            Column(modifier = Modifier.weight(1f)) {\n                Text(\n                    text = item.title,\n                    style = MaterialTheme.typography.titleMedium\n                )\n                Text(\n                    text = item.subtitle,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            }\n\n            Icon(\n                imageVector = Icons.Default.ChevronRight,\n                contentDescription = null,\n                tint = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n    }\n}\n```\n\n## Best Practices\n\n1. **Use Material Theme**: Access colors via `MaterialTheme.colorScheme` for automatic dark mode support\n2. **Support Dynamic Color**: Enable dynamic color on Android 12+ for personalization\n3. **Adaptive Layouts**: Use `WindowSizeClass` for responsive designs\n4. **Content Descriptions**: Add `contentDescription` to all interactive elements\n5. **Touch Targets**: Minimum 48dp touch targets for accessibility\n6. **State Hoisting**: Hoist state to make components reusable and testable\n7. **Remember Properly**: Use `remember` and `rememberSaveable` appropriately\n8. **Preview Annotations**: Add `@Preview` with different configurations\n\n## Common Issues\n\n- **Recomposition Issues**: Avoid passing unstable lambdas; use `remember`\n- **State Loss**: Use `rememberSaveable` for configuration changes\n- **Performance**: Use `LazyColumn` instead of `Column` for long lists\n- **Theme Leaks**: Ensure `MaterialTheme` wraps all composables\n- **Navigation Crashes**: Handle back press and deep links properly\n- **Memory Leaks**: Cancel coroutines in `DisposableEffect`\n\n## Resources\n\n- [Material Design 3](https://m3.material.io/)\n- [Jetpack Compose Documentation](https://developer.android.com/jetpack/compose)\n- [Compose Samples](https://github.com/android/compose-samples)\n- [Material 3 Compose](https://developer.android.com/jetpack/compose/designsystems/material3)\n",".claude/skills/find-skills/SKILL.md":"---\nname: find-skills\ndescription: Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.\n---\n\n# Find Skills\n\nThis skill helps you discover and install skills from the open agent skills ecosystem.\n\n## When to Use This Skill\n\nUse this skill when the user:\n\n- Asks \"how do I do X\" where X might be a common task with an existing skill\n- Says \"find a skill for X\" or \"is there a skill for X\"\n- Asks \"can you do X\" where X is a specialized capability\n- Expresses interest in extending agent capabilities\n- Wants to search for tools, templates, or workflows\n- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)\n\n## What is the Skills CLI?\n\nThe Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.\n\n**Key commands:**\n\n- `npx skills find [query]` - Search for skills interactively or by keyword\n- `npx skills add <package>` - Install a skill from GitHub or other sources\n- `npx skills check` - Check for skill updates\n- `npx skills update` - Update all installed skills\n\n**Browse skills at:** https://skills.sh/\n\n## How to Help Users Find Skills\n\n### Step 1: Understand What They Need\n\nWhen a user asks for help with something, identify:\n\n1. The domain (e.g., React, testing, design, deployment)\n2. The specific task (e.g., writing tests, creating animations, reviewing PRs)\n3. Whether this is a common enough task that a skill likely exists\n\n### Step 2: Search for Skills\n\nRun the find command with a relevant query:\n\n```bash\nnpx skills find [query]\n```\n\nFor example:\n\n- User asks \"how do I make my React app faster?\" → `npx skills find react performance`\n- User asks \"can you help me with PR reviews?\" → `npx skills find pr review`\n- User asks \"I need to create a changelog\" → `npx skills find changelog`\n\nThe command will return results like:\n\n```\nInstall with npx skills add <owner/repo@skill>\n\nvercel-labs/agent-skills@vercel-react-best-practices\n└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 3: Present Options to the User\n\nWhen you find relevant skills, present them to the user with:\n\n1. The skill name and what it does\n2. The install command they can run\n3. A link to learn more at skills.sh\n\nExample response:\n\n```\nI found a skill that might help! The \"vercel-react-best-practices\" skill provides\nReact and Next.js performance optimization guidelines from Vercel Engineering.\n\nTo install it:\nnpx skills add vercel-labs/agent-skills@vercel-react-best-practices\n\nLearn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 4: Offer to Install\n\nIf the user wants to proceed, you can install the skill for them:\n\n```bash\nnpx skills add <owner/repo@skill> -g -y\n```\n\nThe `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.\n\n## Common Skill Categories\n\nWhen searching, consider these common categories:\n\n| Category        | Example Queries                          |\n| --------------- | ---------------------------------------- |\n| Web Development | react, nextjs, typescript, css, tailwind |\n| Testing         | testing, jest, playwright, e2e           |\n| DevOps          | deploy, docker, kubernetes, ci-cd        |\n| Documentation   | docs, readme, changelog, api-docs        |\n| Code Quality    | review, lint, refactor, best-practices   |\n| Design          | ui, ux, design-system, accessibility     |\n| Productivity    | workflow, automation, git                |\n\n## Tips for Effective Searches\n\n1. **Use specific keywords**: \"react testing\" is better than just \"testing\"\n2. **Try alternative terms**: If \"deploy\" doesn't work, try \"deployment\" or \"ci-cd\"\n3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`\n\n## When No Skills Are Found\n\nIf no relevant skills exist:\n\n1. Acknowledge that no existing skill was found\n2. Offer to help with the task directly using your general capabilities\n3. Suggest the user could create their own skill with `npx skills init`\n\nExample:\n\n```\nI searched for skills related to \"xyz\" but didn't find any matches.\nI can still help you with this task directly! Would you like me to proceed?\n\nIf this is something you do often, you could create your own skill:\nnpx skills init my-xyz-skill\n```\n",".claude/skills/compose-navigation/SKILL.md":"# compose-navigation\n\nPath: `.claude/skills/compose-navigation/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/compose-navigation/SKILL.md)",".claude/skills/coil-compose/SKILL.md":"# coil-compose\n\nPath: `.claude/skills/coil-compose/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/coil-compose/SKILL.md)",".claude/skills/android-viewmodel/SKILL.md":"# android-viewmodel\n\nPath: `.claude/skills/android-viewmodel/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-viewmodel/SKILL.md)",".claude/skills/android-testing/SKILL.md":"# android-testing\n\nPath: `.claude/skills/android-testing/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-testing/SKILL.md)",".claude/skills/android-retrofit/SKILL.md":"# android-retrofit\n\nPath: `.claude/skills/android-retrofit/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-retrofit/SKILL.md)",".claude/skills/android-gradle-logic/SKILL.md":"# android-gradle-logic\n\nPath: `.claude/skills/android-gradle-logic/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-gradle-logic/SKILL.md)",".claude/skills/android-emulator-skill/SKILL.md":"# android-emulator-skill\n\nPath: `.claude/skills/android-emulator-skill/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/SKILL.md)",".claude/skills/android-data-layer/SKILL.md":"# android-data-layer\n\nPath: `.claude/skills/android-data-layer/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-data-layer/SKILL.md)",".claude/skills/android-coroutines/SKILL.md":"# android-coroutines\n\nPath: `.claude/skills/android-coroutines/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-coroutines/SKILL.md)",".claude/skills/android-architecture/SKILL.md":"# android-architecture\n\nPath: `.claude/skills/android-architecture/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-architecture/SKILL.md)",".claude/skills/android-accessibility/SKILL.md":"# android-accessibility\n\nPath: `.claude/skills/android-accessibility/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-accessibility/SKILL.md)","CLAUDE.md":"# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/CLAUDE.md)",".claude/agents/mobile-developer.md":"# Subagent: mobile-developer\n\nPath: `.claude/agents/mobile-developer.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/agents/mobile-developer.md)",".claude/skills/ui-ux-pro-max/scripts/core.py":"# Claude Directive: core.py\n\nPath: `.claude/skills/ui-ux-pro-max/scripts/core.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/scripts/core.py)",".claude/skills/ui-ux-pro-max/scripts/search.py":"# Claude Directive: search.py\n\nPath: `.claude/skills/ui-ux-pro-max/scripts/search.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/scripts/search.py)",".claude/skills/ui-ux-pro-max/data/charts.csv":"# Claude Directive: charts.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/charts.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/charts.csv)",".claude/skills/ui-ux-pro-max/data/colors.csv":"# Claude Directive: colors.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/colors.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/colors.csv)",".claude/skills/ui-ux-pro-max/data/icons.csv":"# Claude Directive: icons.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/icons.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/icons.csv)",".claude/skills/ui-ux-pro-max/data/landing.csv":"# Claude Directive: landing.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/landing.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/landing.csv)",".claude/skills/ui-ux-pro-max/data/products.csv":"# Claude Directive: products.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/products.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/products.csv)",".claude/skills/ui-ux-pro-max/data/prompts.csv":"# Claude Directive: prompts.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/prompts.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/prompts.csv)",".claude/skills/ui-ux-pro-max/data/styles.csv":"# Claude Directive: styles.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/styles.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/styles.csv)",".claude/skills/ui-ux-pro-max/data/typography.csv":"# Claude Directive: typography.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/typography.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/typography.csv)",".claude/skills/ui-ux-pro-max/data/ux-guidelines.csv":"# Claude Directive: ux-guidelines.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv)",".claude/skills/mobile-android-design/references/android-navigation.md":"# References Skill\n\nPath: `.claude/skills/mobile-android-design/references/android-navigation.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/android-navigation.md)",".claude/skills/mobile-android-design/references/compose-components.md":"# References Skill\n\nPath: `.claude/skills/mobile-android-design/references/compose-components.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/compose-components.md)",".claude/skills/mobile-android-design/references/material3-theming.md":"# References Skill\n\nPath: `.claude/skills/mobile-android-design/references/material3-theming.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/material3-theming.md)",".claude/skills/marketing-ideas/references/ideas-by-category.md":"# References Skill\n\nPath: `.claude/skills/marketing-ideas/references/ideas-by-category.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/marketing-ideas/references/ideas-by-category.md)",".claude/skills/kmp/scripts/suggest-kmp-dependency.sh":"# Claude Directive: suggest-kmp-dependency.sh\n\nPath: `.claude/skills/kmp/scripts/suggest-kmp-dependency.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/scripts/suggest-kmp-dependency.sh)",".claude/skills/kmp/scripts/validate-kmp-structure.sh":"# Claude Directive: validate-kmp-structure.sh\n\nPath: `.claude/skills/kmp/scripts/validate-kmp-structure.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/scripts/validate-kmp-structure.sh)",".claude/skills/kmp/references/abstraction-examples.md":"# References Skill\n\nPath: `.claude/skills/kmp/references/abstraction-examples.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/abstraction-examples.md)",".claude/skills/kmp/references/expect-actual-catalog.md":"# References Skill\n\nPath: `.claude/skills/kmp/references/expect-actual-catalog.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/expect-actual-catalog.md)",".claude/skills/kmp/references/source-set-hierarchy.md":"# References Skill\n\nPath: `.claude/skills/kmp/references/source-set-hierarchy.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/source-set-hierarchy.md)",".claude/skills/kmp/references/target-compatibility.md":"# References Skill\n\nPath: `.claude/skills/kmp/references/target-compatibility.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/target-compatibility.md)",".claude/skills/android-emulator-skill/scripts/app_launcher.py":"# Claude Directive: app_launcher.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/app_launcher.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/app_launcher.py)",".claude/skills/android-emulator-skill/scripts/build_and_test.py":"# Claude Directive: build_and_test.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/build_and_test.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/build_and_test.py)",".claude/skills/android-emulator-skill/scripts/common.py":"# Claude Directive: common.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/common.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/common.py)",".claude/skills/android-emulator-skill/scripts/emu_health_check.sh":"# Claude Directive: emu_health_check.sh\n\nPath: `.claude/skills/android-emulator-skill/scripts/emu_health_check.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/emu_health_check.sh)",".claude/skills/android-emulator-skill/scripts/emulator_manage.py":"# Claude Directive: emulator_manage.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/emulator_manage.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/emulator_manage.py)",".claude/skills/android-emulator-skill/scripts/gesture.py":"# Claude Directive: gesture.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/gesture.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/gesture.py)",".claude/skills/android-emulator-skill/scripts/keyboard.py":"# Claude Directive: keyboard.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/keyboard.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/keyboard.py)",".claude/skills/android-emulator-skill/scripts/log_monitor.py":"# Claude Directive: log_monitor.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/log_monitor.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/log_monitor.py)",".claude/skills/android-emulator-skill/scripts/navigator.py":"# Claude Directive: navigator.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/navigator.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/navigator.py)",".claude/skills/android-emulator-skill/scripts/screen_mapper.py":"# Claude Directive: screen_mapper.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/screen_mapper.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/screen_mapper.py)",".claude/skills/ui-ux-pro-max/data/stacks/flutter.csv":"# Claude Directive: flutter.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv)",".claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv":"# Claude Directive: html-tailwind.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv)",".claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv":"# Claude Directive: nextjs.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv)",".claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv":"# Claude Directive: nuxt-ui.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv)",".claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv":"# Claude Directive: nuxtjs.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv)",".claude/skills/ui-ux-pro-max/data/stacks/react.csv":"# Claude Directive: react.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/react.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/react.csv)",".claude/skills/ui-ux-pro-max/data/stacks/react-native.csv":"# Claude Directive: react-native.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv)",".claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv":"# Claude Directive: shadcn.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv)",".claude/skills/ui-ux-pro-max/data/stacks/svelte.csv":"# Claude Directive: svelte.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv)",".claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv":"# Claude Directive: swiftui.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv)",".claude/skills/ui-ux-pro-max/data/stacks/vue.csv":"# Claude Directive: vue.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/vue.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv)"},"items":[{"name":"SKILL.md","path":".claude/skills/android-accessibility/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-accessibility/SKILL.md","title":"android-accessibility","category":"anthropic-skill","format":"markdown","content":"# android-accessibility\n\nPath: `.claude/skills/android-accessibility/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-accessibility/SKILL.md)","tokens":300,"sizeBytes":216},{"name":"SKILL.md","path":".claude/skills/android-architecture/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-architecture/SKILL.md","title":"android-architecture","category":"anthropic-skill","format":"markdown","content":"# android-architecture\n\nPath: `.claude/skills/android-architecture/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-architecture/SKILL.md)","tokens":300,"sizeBytes":213},{"name":"SKILL.md","path":".claude/skills/android-coroutines/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-coroutines/SKILL.md","title":"android-coroutines","category":"anthropic-skill","format":"markdown","content":"# android-coroutines\n\nPath: `.claude/skills/android-coroutines/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-coroutines/SKILL.md)","tokens":300,"sizeBytes":207},{"name":"SKILL.md","path":".claude/skills/android-data-layer/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-data-layer/SKILL.md","title":"android-data-layer","category":"anthropic-skill","format":"markdown","content":"# android-data-layer\n\nPath: `.claude/skills/android-data-layer/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-data-layer/SKILL.md)","tokens":300,"sizeBytes":207},{"name":"SKILL.md","path":".claude/skills/android-emulator-skill/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/SKILL.md","title":"android-emulator-skill","category":"anthropic-skill","format":"markdown","content":"# android-emulator-skill\n\nPath: `.claude/skills/android-emulator-skill/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/SKILL.md)","tokens":300,"sizeBytes":219},{"name":"SKILL.md","path":".claude/skills/android-gradle-logic/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-gradle-logic/SKILL.md","title":"android-gradle-logic","category":"anthropic-skill","format":"markdown","content":"# android-gradle-logic\n\nPath: `.claude/skills/android-gradle-logic/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-gradle-logic/SKILL.md)","tokens":300,"sizeBytes":213},{"name":"SKILL.md","path":".claude/skills/android-retrofit/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-retrofit/SKILL.md","title":"android-retrofit","category":"anthropic-skill","format":"markdown","content":"# android-retrofit\n\nPath: `.claude/skills/android-retrofit/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-retrofit/SKILL.md)","tokens":300,"sizeBytes":201},{"name":"SKILL.md","path":".claude/skills/android-testing/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-testing/SKILL.md","title":"android-testing","category":"anthropic-skill","format":"markdown","content":"# android-testing\n\nPath: `.claude/skills/android-testing/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-testing/SKILL.md)","tokens":300,"sizeBytes":198},{"name":"SKILL.md","path":".claude/skills/android-viewmodel/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-viewmodel/SKILL.md","title":"android-viewmodel","category":"anthropic-skill","format":"markdown","content":"# android-viewmodel\n\nPath: `.claude/skills/android-viewmodel/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-viewmodel/SKILL.md)","tokens":300,"sizeBytes":204},{"name":"SKILL.md","path":".claude/skills/coil-compose/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/coil-compose/SKILL.md","title":"coil-compose","category":"anthropic-skill","format":"markdown","content":"# coil-compose\n\nPath: `.claude/skills/coil-compose/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/coil-compose/SKILL.md)","tokens":300,"sizeBytes":189},{"name":"SKILL.md","path":".claude/skills/compose-navigation/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/compose-navigation/SKILL.md","title":"compose-navigation","category":"anthropic-skill","format":"markdown","content":"# compose-navigation\n\nPath: `.claude/skills/compose-navigation/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/compose-navigation/SKILL.md)","tokens":300,"sizeBytes":207},{"name":"SKILL.md","path":".claude/skills/compose-performance-audit/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/compose-performance-audit/SKILL.md","title":"compose-performance-audit","category":"anthropic-skill","format":"markdown","content":"---\nname: compose-performance-audit\ndescription: Audit and improve Jetpack Compose runtime performance from code review and architecture. Use when asked to diagnose slow rendering, janky scrolling, excessive recompositions, or performance issues in Compose UI.\n---\n\n# Compose Performance Audit\n\n## Overview\n\nAudit Jetpack Compose view performance end-to-end, from instrumentation and baselining to root-cause analysis and concrete remediation steps.\n\n## Workflow Decision Tree\n\n- If the user provides code, start with \"Code-First Review.\"\n- If the user only describes symptoms, ask for minimal code/context, then do \"Code-First Review.\"\n- If code review is inconclusive, go to \"Guide the User to Profile\" and ask for Layout Inspector output or Perfetto traces.\n\n## 1. Code-First Review\n\nCollect:\n- Target Composable code.\n- Data flow: state, remember, derived state, ViewModel connections.\n- Symptoms and reproduction steps.\n\nFocus on:\n- **Recomposition storms** from unstable parameters or broad state changes.\n- **Unstable keys** in `LazyColumn`/`LazyRow` (`key` churn, missing keys).\n- **Heavy work in composition** (formatting, sorting, filtering, object allocation).\n- **Unnecessary recompositions** (missing `remember`, unstable classes, lambdas).\n- **Large images** without proper sizing or async loading.\n- **Layout thrash** (deep nesting, intrinsic measurements, `SubcomposeLayout` misuse).\n\nProvide:\n- Likely root causes with code references.\n- Suggested fixes and refactors.\n- If needed, a minimal repro or instrumentation suggestion.\n\n## 2. Guide the User to Profile\n\nExplain how to collect data:\n- Use **Layout Inspector** in Android Studio to see recomposition counts.\n- Enable **Recomposition Highlights** in Compose tooling.\n- Use **Perfetto** or **System Trace** for frame timing analysis.\n- Check **Macrobenchmark** results for startup/scroll metrics.\n\nAsk for:\n- Layout Inspector screenshot showing recomposition counts.\n- Perfetto trace or System Trace export.\n- Device/OS/build configuration (debug vs release).\n\n> **Important**: Ensure profiling is done on a **release build** with R8 enabled. Debug builds have significant overhead.\n\n## 3. Analyze and Diagnose\n\nPrioritize likely Compose culprits:\n- **Recomposition storms** from unstable parameters or broad state changes.\n- **Unstable keys** in lazy lists (`key` churn, index-based keys).\n- **Heavy work in composition** (formatting, sorting, object allocation).\n- **Missing `remember`** causing recreations on every recomposition.\n- **Large images** without `Modifier.size()` constraints.\n- **Unnecessary state reads** in wrong composition phases.\n\nSummarize findings with evidence from traces/Layout Inspector.\n\n## 4. Remediate\n\nApply targeted fixes:\n- **Stabilize parameters**: Use `@Stable` or `@Immutable` annotations on data classes.\n- **Stabilize keys**: Use stable, unique IDs for `LazyColumn`/`LazyRow` items.\n- **Defer state reads**: Use `derivedStateOf`, lambda-based modifiers, or `Modifier.drawBehind`.\n- **Remember expensive computations**: Wrap in `remember { }` or `remember(key) { }`.\n- **Skip recomposition**: Extract stable composables, use `key()` to control identity.\n- **Async image loading**: Use Coil/Glide with proper sizing constraints.\n- **Reduce layout complexity**: Flatten hierarchies, avoid deep nesting.\n\n## Common Code Smells (and Fixes)\n\n### Unstable lambda captures\n\n```kotlin\n// BAD: New lambda instance every recomposition\nButton(onClick = { viewModel.doSomething(item) }) { ... }\n\n// GOOD: Use remember or method reference\nval onClick = remember(item) { { viewModel.doSomething(item) } }\nButton(onClick = onClick) { ... }\n```\n\n### Expensive work in composition\n\n```kotlin\n// BAD: Sorting on every recomposition\n@Composable\nfun ItemList(items: List<Item>) {\n    val sorted = items.sortedBy { it.name } // Runs every recomposition\n    LazyColumn { items(sorted) { ... } }\n}\n\n// GOOD: Use remember with key\n@Composable\nfun ItemList(items: List<Item>) {\n    val sorted = remember(items) { items.sortedBy { it.name } }\n    LazyColumn { items(sorted) { ... } }\n}\n```\n\n### Missing keys in LazyColumn\n\n```kotlin\n// BAD: Index-based identity (causes recomposition on list changes)\nLazyColumn {\n    items(items) { item -> ItemRow(item) }\n}\n\n// GOOD: Stable key-based identity\nLazyColumn {\n    items(items, key = { it.id }) { item -> ItemRow(item) }\n}\n```\n\n### Unstable data classes\n\n```kotlin\n// BAD: Unstable (contains List, which is not stable)\ndata class UiState(\n    val items: List<Item>,\n    val isLoading: Boolean\n)\n\n// GOOD: Mark as Immutable if truly immutable\n@Immutable\ndata class UiState(\n    val items: ImmutableList<Item>, // kotlinx.collections.immutable\n    val isLoading: Boolean\n)\n```\n\n### Reading state too early\n\n```kotlin\n// BAD: State read during composition (recomposes whole tree)\n@Composable\nfun AnimatedBox(scrollState: ScrollState) {\n    val offset = scrollState.value // Recomposes on every scroll\n    Box(modifier = Modifier.offset(y = offset.dp)) { ... }\n}\n\n// GOOD: Defer state read to layout/draw phase\n@Composable\nfun AnimatedBox(scrollState: ScrollState) {\n    Box(modifier = Modifier.offset {\n        IntOffset(0, scrollState.value) // Read in layout phase\n    }) { ... }\n}\n```\n\n### Object allocation in composition\n\n```kotlin\n// BAD: Creates new Modifier chain every recomposition\nBox(modifier = Modifier.padding(16.dp).background(Color.Red))\n\n// GOOD for dynamic modifiers: Remember the modifier\nval modifier = remember { Modifier.padding(16.dp).background(Color.Red) }\nBox(modifier = modifier)\n```\n\n## Stability Checklist\n\n| Type | Stable by Default? | Fix |\n|------|-------------------|-----|\n| Primitives (`Int`, `String`, `Boolean`) | Yes | N/A |\n| `data class` with stable fields | Yes* | Ensure all fields are stable |\n| `List`, `Map`, `Set` | **No** | Use `ImmutableList` from kotlinx |\n| Classes with `var` properties | **No** | Use `@Stable` if externally stable |\n| Lambdas | **No** | Use `remember { }` |\n\n## 5. Verify\n\nAsk the user to:\n- Re-run Layout Inspector and compare recomposition counts.\n- Run Macrobenchmark and compare frame timing.\n- Test on a real device with release build.\n\nSummarize the delta (recomposition count, frame drops, jank) if provided.\n\n## Outputs\n\nProvide:\n- A short metrics table (before/after if available).\n- Top issues (ordered by impact).\n- Proposed fixes with estimated effort.\n\n## References\n\n- [Jetpack Compose Performance](https://developer.android.com/develop/ui/compose/performance)\n- [Compose Stability Explained](https://developer.android.com/develop/ui/compose/performance/stability)\n- [Debugging Recomposition](https://developer.android.com/develop/ui/compose/tooling/layout-inspector)\n- [Macrobenchmark](https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview)\n","frontmatter":{"name":"compose-performance-audit","description":"Audit and improve Jetpack Compose runtime performance from code review and architecture. Use when asked to diagnose slow rendering, janky scrolling, excessive recompositions, or performance issues in Compose UI."},"isInternal":false,"tokens":1580,"sizeBytes":6785},{"name":"SKILL.md","path":".claude/skills/compose-ui/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/compose-ui/SKILL.md","title":"compose-ui","category":"anthropic-skill","format":"markdown","content":"---\nname: compose-ui\ndescription: Best practices for building UI with Jetpack Compose, focusing on state hoisting, detailed performance optimizations, and theming. Use this when writing or refactoring Composable functions.\n---\n\n# Jetpack Compose Best Practices\n\n## Instructions\n\nFollow these guidelines to create performant, reusable, and testable Composables.\n\n### 1. State Hoisting (Unidirectional Data Flow)\nMake Composables **stateless** whenever possible by moving state to the caller.\n\n*   **Pattern**: Function signature should usually look like:\n    ```kotlin\n    @Composable\n    fun MyComponent(\n        value: String,              // State flows down\n        onValueChange: (String) -> Unit, // Events flow up\n        modifier: Modifier = Modifier // Standard modifier parameter\n    )\n    ```\n*   **Benefit**: Decouples the UI from simple state storage, making it easier to preview and test.\n*   **ViewModel Integration**: The screen-level Composable retrieves state from the ViewModel (`viewModel.uiState.collectAsStateWithLifecycle()`) and passes it down.\n\n### 2. Modifiers\n*   **Default Parameter**: Always provide a `modifier: Modifier = Modifier` as the first optional parameter.\n*   **Application**: Apply this `modifier` to the *root* layout element of your Composable.\n*   **Ordering matters**: `padding().clickable()` is different from `clickable().padding()`. Generally apply layout-affecting modifiers (like padding) *after* click listeners if you want the padding to be clickable.\n\n### 3. Performance Optimization\n*   **`remember`**: Use `remember { ... }` to cache expensive calculations across recompositions.\n*   **`derivedStateOf`**: Use `derivedStateOf { ... }` when a state changes frequently (like scroll position) but the UI only needs to react to a threshold or summary (e.g., show \"Jump to Top\" button). This prevents unnecessary recompositions.\n    ```kotlin\n    val showButton by remember {\n        derivedStateOf { listState.firstVisibleItemIndex > 0 }\n    }\n    ```\n*   **Lambda Stability**: Prefer method references (e.g., `viewModel::onEvent`) or remembered lambdas to prevent unstable types from triggering recomposition of children.\n\n### 4. Theming and Resources\n*   Use `MaterialTheme.colorScheme` and `MaterialTheme.typography` instead of hardcoded colors or text styles.\n*   Organize simple UI components into specific files (e.g., `DesignSystem.kt` or `Components.kt`) if they are shared across features.\n\n### 5. Previews\n*   Create a private preview function for every public Composable.\n*   Use `@Preview(showBackground = true)` and include Light/Dark mode previews if applicable.\n*   Pass dummy data (static) to the stateless Composable for the preview.\n","frontmatter":{"name":"compose-ui","description":"Best practices for building UI with Jetpack Compose, focusing on state hoisting, detailed performance optimizations, and theming. Use this when writing or refactoring Composable functions."},"isInternal":false,"tokens":602,"sizeBytes":2701},{"name":"SKILL.md","path":".claude/skills/find-skills/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/find-skills/SKILL.md","title":"find-skills","category":"anthropic-skill","format":"markdown","content":"---\nname: find-skills\ndescription: Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.\n---\n\n# Find Skills\n\nThis skill helps you discover and install skills from the open agent skills ecosystem.\n\n## When to Use This Skill\n\nUse this skill when the user:\n\n- Asks \"how do I do X\" where X might be a common task with an existing skill\n- Says \"find a skill for X\" or \"is there a skill for X\"\n- Asks \"can you do X\" where X is a specialized capability\n- Expresses interest in extending agent capabilities\n- Wants to search for tools, templates, or workflows\n- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)\n\n## What is the Skills CLI?\n\nThe Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.\n\n**Key commands:**\n\n- `npx skills find [query]` - Search for skills interactively or by keyword\n- `npx skills add <package>` - Install a skill from GitHub or other sources\n- `npx skills check` - Check for skill updates\n- `npx skills update` - Update all installed skills\n\n**Browse skills at:** https://skills.sh/\n\n## How to Help Users Find Skills\n\n### Step 1: Understand What They Need\n\nWhen a user asks for help with something, identify:\n\n1. The domain (e.g., React, testing, design, deployment)\n2. The specific task (e.g., writing tests, creating animations, reviewing PRs)\n3. Whether this is a common enough task that a skill likely exists\n\n### Step 2: Search for Skills\n\nRun the find command with a relevant query:\n\n```bash\nnpx skills find [query]\n```\n\nFor example:\n\n- User asks \"how do I make my React app faster?\" → `npx skills find react performance`\n- User asks \"can you help me with PR reviews?\" → `npx skills find pr review`\n- User asks \"I need to create a changelog\" → `npx skills find changelog`\n\nThe command will return results like:\n\n```\nInstall with npx skills add <owner/repo@skill>\n\nvercel-labs/agent-skills@vercel-react-best-practices\n└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 3: Present Options to the User\n\nWhen you find relevant skills, present them to the user with:\n\n1. The skill name and what it does\n2. The install command they can run\n3. A link to learn more at skills.sh\n\nExample response:\n\n```\nI found a skill that might help! The \"vercel-react-best-practices\" skill provides\nReact and Next.js performance optimization guidelines from Vercel Engineering.\n\nTo install it:\nnpx skills add vercel-labs/agent-skills@vercel-react-best-practices\n\nLearn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 4: Offer to Install\n\nIf the user wants to proceed, you can install the skill for them:\n\n```bash\nnpx skills add <owner/repo@skill> -g -y\n```\n\nThe `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.\n\n## Common Skill Categories\n\nWhen searching, consider these common categories:\n\n| Category        | Example Queries                          |\n| --------------- | ---------------------------------------- |\n| Web Development | react, nextjs, typescript, css, tailwind |\n| Testing         | testing, jest, playwright, e2e           |\n| DevOps          | deploy, docker, kubernetes, ci-cd        |\n| Documentation   | docs, readme, changelog, api-docs        |\n| Code Quality    | review, lint, refactor, best-practices   |\n| Design          | ui, ux, design-system, accessibility     |\n| Productivity    | workflow, automation, git                |\n\n## Tips for Effective Searches\n\n1. **Use specific keywords**: \"react testing\" is better than just \"testing\"\n2. **Try alternative terms**: If \"deploy\" doesn't work, try \"deployment\" or \"ci-cd\"\n3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`\n\n## When No Skills Are Found\n\nIf no relevant skills exist:\n\n1. Acknowledge that no existing skill was found\n2. Offer to help with the task directly using your general capabilities\n3. Suggest the user could create their own skill with `npx skills init`\n\nExample:\n\n```\nI searched for skills related to \"xyz\" but didn't find any matches.\nI can still help you with this task directly! Would you like me to proceed?\n\nIf this is something you do often, you could create your own skill:\nnpx skills init my-xyz-skill\n```\n","frontmatter":{"name":"find-skills","description":"Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill."},"isInternal":false,"tokens":1110,"sizeBytes":4635},{"name":"SKILL.md","path":".claude/skills/gradle-build-performance/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/gradle-build-performance/SKILL.md","title":"gradle-build-performance","category":"anthropic-skill","format":"markdown","content":"---\nname: gradle-build-performance\ndescription: Debug and optimize Android/Gradle build performance. Use when builds are slow, investigating CI/CD performance, analyzing build scans, or identifying compilation bottlenecks.\n---\n\n# Gradle Build Performance\n\n## When to Use\n\n- Build times are slow (clean or incremental)\n- Investigating build performance regressions\n- Analyzing Gradle Build Scans\n- Identifying configuration vs execution bottlenecks\n- Optimizing CI/CD build times\n- Enabling Gradle Configuration Cache\n- Reducing unnecessary recompilation\n- Debugging kapt/KSP annotation processing\n\n## Example Prompts\n\n- \"My builds are slow, how can I speed them up?\"\n- \"How do I analyze a Gradle build scan?\"\n- \"Why is configuration taking so long?\"\n- \"Why does my project always recompile everything?\"\n- \"How do I enable configuration cache?\"\n- \"Why is kapt so slow?\"\n\n---\n\n## Workflow\n\n1. **Measure Baseline** — Clean build + incremental build times\n2. **Generate Build Scan** — `./gradlew assembleDebug --scan`\n3. **Identify Phase** — Configuration? Execution? Dependency resolution?\n4. **Apply ONE optimization** — Don't batch changes\n5. **Measure Improvement** — Compare against baseline\n6. **Verify in Build Scan** — Visual confirmation\n\n---\n\n## Quick Diagnostics\n\n### Generate Build Scan\n\n```bash\n./gradlew assembleDebug --scan\n```\n\n### Profile Build Locally\n\n```bash\n./gradlew assembleDebug --profile\n# Opens report in build/reports/profile/\n```\n\n### Build Timing Summary\n\n```bash\n./gradlew assembleDebug --info | grep -E \"^\\:.*\"\n# Or view in Android Studio: Build > Analyze APK Build\n```\n\n---\n\n## Build Phases\n\n| Phase | What Happens | Common Issues |\n|-------|--------------|---------------|\n| **Initialization** | `settings.gradle.kts` evaluated | Too many `include()` statements |\n| **Configuration** | All `build.gradle.kts` files evaluated | Expensive plugins, eager task creation |\n| **Execution** | Tasks run based on inputs/outputs | Cache misses, non-incremental tasks |\n\n### Identify the Bottleneck\n\n```\nBuild scan → Performance → Build timeline\n```\n\n- **Long configuration phase**: Focus on plugin and buildscript optimization\n- **Long execution phase**: Focus on task caching and parallelization\n- **Dependency resolution slow**: Focus on repository configuration\n\n---\n\n## 12 Optimization Patterns\n\n### 1. Enable Configuration Cache\n\nCaches configuration phase across builds (AGP 8.0+):\n\n```properties\n# gradle.properties\norg.gradle.configuration-cache=true\norg.gradle.configuration-cache.problems=warn\n```\n\n### 2. Enable Build Cache\n\nReuses task outputs across builds and machines:\n\n```properties\n# gradle.properties\norg.gradle.caching=true\n```\n\n### 3. Enable Parallel Execution\n\nBuild independent modules simultaneously:\n\n```properties\n# gradle.properties\norg.gradle.parallel=true\n```\n\n### 4. Increase JVM Heap\n\nAllocate more memory for large projects:\n\n```properties\n# gradle.properties\norg.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC\n```\n\n### 5. Use Non-Transitive R Classes\n\nReduces R class size and compilation (AGP 8.0+ default):\n\n```properties\n# gradle.properties\nandroid.nonTransitiveRClass=true\n```\n\n### 6. Migrate kapt to KSP\n\nKSP is 2x faster than kapt for Kotlin:\n\n```kotlin\n// Before (slow)\nkapt(\"com.google.dagger:hilt-compiler:2.51.1\")\n\n// After (fast)\nksp(\"com.google.dagger:hilt-compiler:2.51.1\")\n```\n\n### 7. Avoid Dynamic Dependencies\n\nPin dependency versions:\n\n```kotlin\n// BAD: Forces resolution every build\nimplementation(\"com.example:lib:+\")\nimplementation(\"com.example:lib:1.0.+\")\n\n// GOOD: Fixed version\nimplementation(\"com.example:lib:1.2.3\")\n```\n\n### 8. Optimize Repository Order\n\nPut most-used repositories first:\n\n```kotlin\n// settings.gradle.kts\ndependencyResolutionManagement {\n    repositories {\n        google()      // First: Android dependencies\n        mavenCentral() // Second: Most libraries\n        // Third-party repos last\n    }\n}\n```\n\n### 9. Use includeBuild for Local Modules\n\nComposite builds are faster than `project()` for large monorepos:\n\n```kotlin\n// settings.gradle.kts\nincludeBuild(\"shared-library\") {\n    dependencySubstitution {\n        substitute(module(\"com.example:shared\")).using(project(\":\"))\n    }\n}\n```\n\n### 10. Enable Incremental Annotation Processing\n\n```properties\n# gradle.properties\nkapt.incremental.apt=true\nkapt.use.worker.api=true\n```\n\n### 11. Avoid Configuration-Time I/O\n\nDon't read files or make network calls during configuration:\n\n```kotlin\n// BAD: Runs during configuration\nval version = file(\"version.txt\").readText()\n\n// GOOD: Defer to execution\nval version = providers.fileContents(file(\"version.txt\")).asText\n```\n\n### 12. Use Lazy Task Configuration\n\nAvoid `create()`, use `register()`:\n\n```kotlin\n// BAD: Eagerly configured\ntasks.create(\"myTask\") { ... }\n\n// GOOD: Lazily configured\ntasks.register(\"myTask\") { ... }\n```\n\n---\n\n## Common Bottleneck Analysis\n\n### Slow Configuration Phase\n\n**Symptoms**: Build scan shows long \"Configuring build\" time\n\n**Causes & Fixes**:\n| Cause | Fix |\n|-------|-----|\n| Eager task creation | Use `tasks.register()` instead of `tasks.create()` |\n| buildSrc with many dependencies | Migrate to Convention Plugins with `includeBuild` |\n| File I/O in build scripts | Use `providers.fileContents()` |\n| Network calls in plugins | Cache results or use offline mode |\n\n### Slow Compilation\n\n**Symptoms**: `:app:compileDebugKotlin` takes too long\n\n**Causes & Fixes**:\n| Cause | Fix |\n|-------|-----|\n| Non-incremental changes | Avoid `build.gradle.kts` changes that invalidate cache |\n| Large modules | Break into smaller feature modules |\n| Excessive kapt usage | Migrate to KSP |\n| Kotlin compiler memory | Increase `kotlin.daemon.jvmargs` |\n\n### Cache Misses\n\n**Symptoms**: Tasks always rerun despite no changes\n\n**Causes & Fixes**:\n| Cause | Fix |\n|-------|-----|\n| Unstable task inputs | Use `@PathSensitive`, `@NormalizeLineEndings` |\n| Absolute paths in outputs | Use relative paths |\n| Missing `@CacheableTask` | Add annotation to custom tasks |\n| Different JDK versions | Standardize JDK across environments |\n\n---\n\n## CI/CD Optimizations\n\n### Remote Build Cache\n\n```kotlin\n// settings.gradle.kts\nbuildCache {\n    local { isEnabled = true }\n    remote<HttpBuildCache> {\n        url = uri(\"https://cache.example.com/\")\n        isPush = System.getenv(\"CI\") == \"true\"\n        credentials {\n            username = System.getenv(\"CACHE_USER\")\n            password = System.getenv(\"CACHE_PASS\")\n        }\n    }\n}\n```\n\n### Gradle Enterprise / Develocity\n\nFor advanced build analytics:\n\n```kotlin\n// settings.gradle.kts\nplugins {\n    id(\"com.gradle.develocity\") version \"3.17\"\n}\n\ndevelocity {\n    buildScan {\n        termsOfUseUrl.set(\"https://gradle.com/help/legal-terms-of-use\")\n        termsOfUseAgree.set(\"yes\")\n        publishing.onlyIf { System.getenv(\"CI\") != null }\n    }\n}\n```\n\n### Skip Unnecessary Tasks in CI\n\n```bash\n# Skip tests for UI-only changes\n./gradlew assembleDebug -x test -x lint\n\n# Only run affected module tests\n./gradlew :feature:login:test\n```\n\n---\n\n## Android Studio Settings\n\n### File → Settings → Build → Gradle\n\n- **Gradle JDK**: Match your project's JDK\n- **Build and run using**: Gradle (not IntelliJ)\n- **Run tests using**: Gradle\n\n### File → Settings → Build → Compiler\n\n- **Compile independent modules in parallel**: ✅ Enabled\n- **Configure on demand**: ❌ Disabled (deprecated)\n\n---\n\n## Verification Checklist\n\nAfter optimizations, verify:\n\n- [ ] Configuration cache enabled and working\n- [ ] Build cache hit rate > 80% (check build scan)\n- [ ] No dynamic dependency versions\n- [ ] KSP used instead of kapt where possible\n- [ ] Parallel execution enabled\n- [ ] JVM memory tuned appropriately\n- [ ] CI remote cache configured\n- [ ] No configuration-time I/O\n\n---\n\n## References\n\n- [Optimize Build Speed](https://developer.android.com/build/optimize-your-build)\n- [Gradle Configuration Cache](https://docs.gradle.org/current/userguide/configuration_cache.html)\n- [Gradle Build Cache](https://docs.gradle.org/current/userguide/build_cache.html)\n- [Migrate from kapt to KSP](https://developer.android.com/build/migrate-to-ksp)\n- [Gradle Build Scans](https://scans.gradle.com/)\n","frontmatter":{"name":"gradle-build-performance","description":"Debug and optimize Android/Gradle build performance. Use when builds are slow, investigating CI/CD performance, analyzing build scans, or identifying compilation bottlenecks."},"isInternal":false,"tokens":1956,"sizeBytes":8163},{"name":"SKILL.md","path":".claude/skills/jetpack-compose/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/jetpack-compose/SKILL.md","title":"android-jetpack-compose","category":"anthropic-skill","format":"markdown","content":"---\nname: android-jetpack-compose\ndescription: Use when building Android UIs with Jetpack Compose, managing state with remember/mutableStateOf, or implementing declarative UI patterns.\nallowed-tools:\n  - Read\n  - Write\n  - Edit\n  - Bash\n  - Grep\n  - Glob\n---\n\n# Android - Jetpack Compose\n\nModern declarative UI toolkit for building native Android interfaces.\n\n## Key Concepts\n\n### State Management\n\nCompose provides several ways to manage state:\n\n- **remember**: Survives recomposition\n- **rememberSaveable**: Survives configuration changes\n- **mutableStateOf**: Creates observable state\n- **derivedStateOf**: Computed state that updates when dependencies change\n\n```kotlin\n@Composable\nfun Counter() {\n    var count by remember { mutableStateOf(0) }\n\n    Column {\n        Text(\"Count: $count\")\n        Button(onClick = { count++ }) {\n            Text(\"Increment\")\n        }\n    }\n}\n\n// With saveable for configuration changes\n@Composable\nfun SearchField() {\n    var query by rememberSaveable { mutableStateOf(\"\") }\n\n    TextField(\n        value = query,\n        onValueChange = { query = it },\n        placeholder = { Text(\"Search...\") }\n    )\n}\n```\n\n### State Hoisting\n\nLift state up to make composables stateless and reusable:\n\n```kotlin\n// Stateless composable\n@Composable\nfun NameInput(\n    name: String,\n    onNameChange: (String) -> Unit,\n    modifier: Modifier = Modifier\n) {\n    TextField(\n        value = name,\n        onValueChange = onNameChange,\n        label = { Text(\"Name\") },\n        modifier = modifier\n    )\n}\n\n// Stateful parent\n@Composable\nfun UserForm() {\n    var name by remember { mutableStateOf(\"\") }\n\n    NameInput(\n        name = name,\n        onNameChange = { name = it }\n    )\n}\n```\n\n### ViewModel Integration\n\n```kotlin\nclass UserViewModel : ViewModel() {\n    private val _uiState = MutableStateFlow(UserUiState())\n    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()\n\n    fun updateName(name: String) {\n        _uiState.update { it.copy(name = name) }\n    }\n\n    fun saveUser() {\n        viewModelScope.launch {\n            _uiState.update { it.copy(isLoading = true) }\n            try {\n                userRepository.save(_uiState.value.toUser())\n                _uiState.update { it.copy(isLoading = false, isSaved = true) }\n            } catch (e: Exception) {\n                _uiState.update { it.copy(isLoading = false, error = e.message) }\n            }\n        }\n    }\n}\n\n@Composable\nfun UserScreen(viewModel: UserViewModel = viewModel()) {\n    val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n\n    UserContent(\n        uiState = uiState,\n        onNameChange = viewModel::updateName,\n        onSave = viewModel::saveUser\n    )\n}\n```\n\n## Best Practices\n\n### Composable Function Guidelines\n\n```kotlin\n// Use Modifier as first optional parameter\n@Composable\nfun CustomCard(\n    title: String,\n    modifier: Modifier = Modifier,\n    onClick: () -> Unit = {}\n) {\n    Card(\n        modifier = modifier.clickable(onClick = onClick)\n    ) {\n        Text(\n            text = title,\n            modifier = Modifier.padding(16.dp)\n        )\n    }\n}\n\n// Use slot APIs for flexible content\n@Composable\nfun CustomScaffold(\n    topBar: @Composable () -> Unit = {},\n    bottomBar: @Composable () -> Unit = {},\n    content: @Composable (PaddingValues) -> Unit\n) {\n    Scaffold(\n        topBar = topBar,\n        bottomBar = bottomBar,\n        content = content\n    )\n}\n```\n\n### Efficient Recomposition\n\n```kotlin\n// Use keys for list items\n@Composable\nfun UserList(users: List<User>) {\n    LazyColumn {\n        items(\n            items = users,\n            key = { it.id }  // Stable key for efficient updates\n        ) { user ->\n            UserItem(user)\n        }\n    }\n}\n\n// Use derivedStateOf for expensive computations\n@Composable\nfun FilteredList(items: List<Item>, query: String) {\n    val filteredItems by remember(items, query) {\n        derivedStateOf {\n            items.filter { it.name.contains(query, ignoreCase = true) }\n        }\n    }\n\n    LazyColumn {\n        items(filteredItems) { item ->\n            ItemRow(item)\n        }\n    }\n}\n```\n\n### Side Effects\n\n```kotlin\n// LaunchedEffect for coroutine-based side effects\n@Composable\nfun UserProfile(userId: String, viewModel: UserViewModel) {\n    LaunchedEffect(userId) {\n        viewModel.loadUser(userId)\n    }\n\n    // UI content\n}\n\n// DisposableEffect for cleanup\n@Composable\nfun LifecycleAwareComponent(lifecycle: Lifecycle) {\n    DisposableEffect(lifecycle) {\n        val observer = LifecycleEventObserver { _, event ->\n            // Handle lifecycle events\n        }\n        lifecycle.addObserver(observer)\n\n        onDispose {\n            lifecycle.removeObserver(observer)\n        }\n    }\n}\n\n// SideEffect for non-suspend side effects\n@Composable\nfun AnalyticsScreen(screenName: String) {\n    SideEffect {\n        analytics.logScreenView(screenName)\n    }\n}\n```\n\n## Common Patterns\n\n### Navigation with Navigation Compose\n\n```kotlin\n@Composable\nfun AppNavigation() {\n    val navController = rememberNavController()\n\n    NavHost(navController = navController, startDestination = \"home\") {\n        composable(\"home\") {\n            HomeScreen(\n                onNavigateToDetail = { id ->\n                    navController.navigate(\"detail/$id\")\n                }\n            )\n        }\n        composable(\n            route = \"detail/{itemId}\",\n            arguments = listOf(navArgument(\"itemId\") { type = NavType.StringType })\n        ) { backStackEntry ->\n            val itemId = backStackEntry.arguments?.getString(\"itemId\")\n            DetailScreen(itemId = itemId)\n        }\n    }\n}\n```\n\n### Material 3 Theming\n\n```kotlin\n@Composable\nfun AppTheme(\n    darkTheme: Boolean = isSystemInDarkTheme(),\n    content: @Composable () -> Unit\n) {\n    val colorScheme = when {\n        darkTheme -> darkColorScheme(\n            primary = Purple80,\n            secondary = PurpleGrey80,\n            tertiary = Pink80\n        )\n        else -> lightColorScheme(\n            primary = Purple40,\n            secondary = PurpleGrey40,\n            tertiary = Pink40\n        )\n    }\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        typography = Typography,\n        content = content\n    )\n}\n\n// Using theme values\n@Composable\nfun ThemedCard() {\n    Card(\n        colors = CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surfaceVariant\n        )\n    ) {\n        Text(\n            text = \"Themed content\",\n            style = MaterialTheme.typography.bodyLarge,\n            color = MaterialTheme.colorScheme.onSurfaceVariant\n        )\n    }\n}\n```\n\n### Lists and Grids\n\n```kotlin\n@Composable\nfun ProductGrid(products: List<Product>) {\n    LazyVerticalGrid(\n        columns = GridCells.Adaptive(minSize = 160.dp),\n        contentPadding = PaddingValues(16.dp),\n        horizontalArrangement = Arrangement.spacedBy(16.dp),\n        verticalArrangement = Arrangement.spacedBy(16.dp)\n    ) {\n        items(products, key = { it.id }) { product ->\n            ProductCard(product)\n        }\n    }\n}\n\n// Sticky headers\n@Composable\nfun ContactList(contacts: Map<Char, List<Contact>>) {\n    LazyColumn {\n        contacts.forEach { (initial, contactsForInitial) ->\n            stickyHeader {\n                Text(\n                    text = initial.toString(),\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .background(MaterialTheme.colorScheme.surface)\n                        .padding(16.dp),\n                    style = MaterialTheme.typography.titleMedium\n                )\n            }\n            items(contactsForInitial) { contact ->\n                ContactItem(contact)\n            }\n        }\n    }\n}\n```\n\n## Anti-Patterns\n\n### Avoid Side Effects in Composition\n\nBad:\n\n```kotlin\n@Composable\nfun BadExample(viewModel: ViewModel) {\n    viewModel.loadData()  // Called on every recomposition!\n\n    Text(\"Data loaded\")\n}\n```\n\nGood:\n\n```kotlin\n@Composable\nfun GoodExample(viewModel: ViewModel) {\n    LaunchedEffect(Unit) {\n        viewModel.loadData()\n    }\n\n    Text(\"Data loaded\")\n}\n```\n\n### Don't Read State in Remember Block\n\nBad:\n\n```kotlin\n@Composable\nfun BadCounter(initial: Int) {\n    // Won't update when initial changes\n    var count by remember { mutableStateOf(initial) }\n}\n```\n\nGood:\n\n```kotlin\n@Composable\nfun GoodCounter(initial: Int) {\n    var count by remember(initial) { mutableStateOf(initial) }\n}\n```\n\n### Avoid Heavy Computation During Composition\n\nBad:\n\n```kotlin\n@Composable\nfun BadList(items: List<Item>) {\n    // Runs on every recomposition\n    val sorted = items.sortedBy { it.name }\n    LazyColumn { /* ... */ }\n}\n```\n\nGood:\n\n```kotlin\n@Composable\nfun GoodList(items: List<Item>) {\n    val sorted by remember(items) {\n        derivedStateOf { items.sortedBy { it.name } }\n    }\n    LazyColumn { /* ... */ }\n}\n```\n\n## Related Skills\n\n- **android-architecture**: MVVM and clean architecture patterns\n- **android-kotlin-coroutines**: Async operations in Compose\n","frontmatter":{"name":"android-jetpack-compose","description":"Use when building Android UIs with Jetpack Compose, managing state with remember/mutableStateOf, or implementing declarative UI patterns.","allowed-tools":["Read","Write","Edit","Bash","Grep","Glob"],"allowedTools":["Read","Write","Edit","Bash","Grep","Glob"]},"allowedTools":["Read","Write","Edit","Bash","Grep","Glob"],"isInternal":false,"tokens":2030,"sizeBytes":8991},{"name":"abstraction-examples.md","path":".claude/skills/kmp/references/abstraction-examples.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/abstraction-examples.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/kmp/references/abstraction-examples.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/abstraction-examples.md)","tokens":300,"sizeBytes":227},{"name":"expect-actual-catalog.md","path":".claude/skills/kmp/references/expect-actual-catalog.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/expect-actual-catalog.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/kmp/references/expect-actual-catalog.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/expect-actual-catalog.md)","tokens":300,"sizeBytes":229},{"name":"source-set-hierarchy.md","path":".claude/skills/kmp/references/source-set-hierarchy.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/source-set-hierarchy.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/kmp/references/source-set-hierarchy.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/source-set-hierarchy.md)","tokens":300,"sizeBytes":227},{"name":"target-compatibility.md","path":".claude/skills/kmp/references/target-compatibility.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/target-compatibility.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/kmp/references/target-compatibility.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/target-compatibility.md)","tokens":300,"sizeBytes":227},{"name":"SKILL.md","path":".claude/skills/kmp/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/SKILL.md","title":"kotlin-multiplatform","category":"anthropic-skill","format":"markdown","content":"---\nname: kotlin-multiplatform\ndescription: |\n  Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific,\n  source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets\n  (Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues.\n  Triggers on: abstraction decisions (\"should I share this?\"), source set placement questions, expect/actual creation,\n  build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.\n---\n\n# Kotlin Multiplatform: Platform Abstraction Decisions\n\nExpert guidance for KMP architecture in Amethyst - deciding what to share vs keep platform-specific.\n\n## When to Use This Skill\n\nMaking platform abstraction decisions:\n- \"Should I create expect/actual or keep Android-only?\"\n- \"Can I share this ViewModel logic?\"\n- \"Where does this crypto/JSON/network implementation belong?\"\n- \"This uses Android Context - can it be abstracted?\"\n- \"Is this code in the wrong module?\"\n- Preparing for iOS/web/wasm targets\n- Detecting incorrect placements\n\n## Abstraction Decision Tree\n\n**Central question:** \"Should this code be reused across platforms?\"\n\nFollow this decision path (< 1 minute):\n\n```\nQ: Is it used by 2+ platforms?\n├─ NO  → Keep platform-specific\n│         Example: Android-only permission handling\n│\n└─ YES → Continue ↓\n\nQ: Is it pure Kotlin (no platform APIs)?\n├─ YES → commonMain\n│         Example: Nostr event parsing, business rules\n│\n└─ NO  → Continue ↓\n\nQ: Does it vary by platform or by JVM vs non-JVM?\n├─ By platform (Android ≠ iOS ≠ Desktop)\n│  → expect/actual\n│  Example: Secp256k1Instance (uses different security APIs)\n│\n├─ By JVM (Android = Desktop ≠ iOS/web)\n│  → jvmAndroid\n│  Example: Jackson JSON parsing (JVM library)\n│\n└─ Complex/UI-related\n   → Keep platform-specific\n   Example: Navigation (Activity vs Window too different)\n\nFinal check:\nQ: Maintenance cost of abstraction < duplication cost?\n├─ YES → Proceed with abstraction\n└─ NO  → Duplicate (simpler)\n```\n\n### Real Examples from Codebase\n\n**Crypto → expect/actual:**\n```kotlin\n// commonMain - expect declaration\nexpect object Secp256k1Instance {\n    fun signSchnorr(data: ByteArray, privKey: ByteArray): ByteArray\n}\n\n// androidMain - uses Android Keystore\n// jvmMain - uses Desktop JVM crypto\n// iosMain - uses iOS Security framework\n```\n**Why:** Each platform has different security APIs.\n\n**JSON parsing → jvmAndroid:**\n```kotlin\n// quartz/build.gradle.kts\nval jvmAndroid = create(\"jvmAndroid\") {\n    api(libs.jackson.module.kotlin)\n}\n```\n**Why:** Jackson is JVM-only, works on Android + Desktop, not iOS/web.\n\n**Navigation → platform-specific:**\n- Android: `MainActivity` (Activity + Compose Navigation)\n- Desktop: `Window` + sidebar + MenuBar\n**Why:** UI paradigms fundamentally different.\n\n## Mental Model: Source Sets as Dependency Graph\n\nThink of source sets as a dependency graph, not folders.\n\n```\n┌─────────────────────────────────────────────┐\n│ commonMain = Contract (pure Kotlin)         │\n│ - Business logic, protocol, data models     │\n│ - No platform APIs                          │\n└────────────┬────────────────────────────────┘\n             │\n             ├──────────────────────┬────────────────────\n             │                      │\n             ▼                      ▼\n   ┌───────────────────┐  ┌──────────────────┐\n   │ jvmAndroid        │  │ iosMain          │\n   │ JVM libs shared   │  │ iOS common       │\n   │ - Jackson         │  │                  │\n   │ - OkHttp          │  └────┬─────────────┘\n   └───┬───────────┬───┘       │\n       │           │            ├─→ iosX64Main\n       ▼           ▼            ├─→ iosArm64Main\n  ┌─────────┐ ┌──────────┐     └─→ iosSimulatorArm64Main\n  │android  │ │jvmMain   │\n  │Main     │ │(Desktop) │\n  └─────────┘ └──────────┘\n\nFuture: jsMain, wasmMain\n```\n\n**Key insight:** jvmAndroid is NOT a platform - it's a shared JVM layer.\n\n## The jvmAndroid Pattern\n\n**Unique to Amethyst.** Shares JVM libraries between Android + Desktop.\n\n### When to Use jvmAndroid\n\nUse jvmAndroid when:\n- ✅ JVM-specific libraries (Jackson, OkHttp, url-detector)\n- ✅ Android implementation = Desktop implementation (same JVM)\n- ✅ Library doesn't work on iOS/web\n\nDo NOT use jvmAndroid for:\n- ❌ Pure Kotlin code (use commonMain)\n- ❌ Platform-specific APIs (use androidMain/jvmMain)\n- ❌ Code that should work on all platforms\n\n### Example from quartz/build.gradle.kts\n\n```kotlin\n// Must be defined BEFORE androidMain and jvmMain\nval jvmAndroid = create(\"jvmAndroid\") {\n    dependsOn(commonMain.get())\n\n    dependencies {\n        api(libs.jackson.module.kotlin)  // JSON parsing - JVM only\n        api(libs.url.detector)            // URL extraction - JVM only\n        implementation(libs.okhttp)       // HTTP client - JVM only\n    }\n}\n\n// Both depend on jvmAndroid\njvmMain { dependsOn(jvmAndroid) }\nandroidMain { dependsOn(jvmAndroid) }\n```\n\n**Why Jackson in jvmAndroid, not commonMain?**\n- Jackson is JVM-specific library\n- Works on Android (runs on JVM)\n- Works on Desktop (runs on JVM)\n- Does NOT work on iOS (not JVM) or web (not JVM)\n\n**Web/wasm consideration:** For future web support, consider migrating from Jackson → kotlinx.serialization (see Target-Specific Guidance).\n\n## What to Abstract vs Keep Platform-Specific\n\nQuick decision guidelines based on codebase patterns:\n\n### Always Abstract\n- **Crypto** (Secp256k1, encryption, signing)\n- **Core protocol logic** (Nostr events, NIPs)\n- **Why:** Needed everywhere, platform security APIs vary\n\n### Often Abstract\n- **I/O operations** (file reading, caching)\n- **Logging** (platform logging systems differ)\n- **Serialization** (if using kotlinx.serialization)\n- **Why:** Commonly reused, platform implementations available\n\n### Sometimes Abstract\n- **Business logic:** YES - state machines, data processing\n- **ViewModels:** YES - state + business logic shareable (StateFlow/SharedFlow)\n- **Screen layouts:** NO - platform-native (Window vs Activity)\n- **Why:** ViewModels contain platform-agnostic state; Screens render differently per platform\n\n### Rarely Abstract\n- **Complex UI components** (composables with heavy platform dependencies)\n- **Why:** Platform paradigms can differ significantly\n\n### Never Abstract\n- **Navigation** (Activity vs Window fundamentally different)\n- **Permissions** (Android vs iOS APIs incompatible)\n- **Platform UX patterns**\n- **Why:** Too platform-specific, abstraction creates leaky APIs\n\n### Evidence from shared-ui-analysis.md\n\n| Component | Shared? | Rationale |\n|-----------|---------|-----------|\n| PubKeyFormatter, ZapFormatter | ✅ YES | Pure Kotlin, no platform APIs |\n| TimeAgoFormatter | ⚠️ ABSTRACTED | Needs StringProvider for localized strings |\n| ViewModels (state + logic) | ✅ YES | StateFlow/SharedFlow platform-agnostic, Compose Multiplatform lifecycle compatible |\n| Screen layouts (Scaffold, nav) | ❌ NO | Window vs Activity, sidebar vs bottom nav fundamentally different |\n| Image loading (Coil) | ⚠️ ABSTRACTED | Coil 3.x supports KMP, needs expect/actual wrapper |\n\n## expect/actual Mechanics\n\n**When to use:** Code needed by 2+ platforms, varies by platform.\n\n### Pattern Categories from Codebase\n\n**Objects (singletons):**\n```kotlin\n// 24 expect declarations found, common pattern:\nexpect object Secp256k1Instance { ... }\nexpect object Log { ... }\nexpect object LibSodiumInstance { ... }\n```\n\n**Classes (instantiable):**\n```kotlin\nexpect class AESCBC { ... }\nexpect class DigestInstance { ... }\n```\n\n**Functions (utilities):**\n```kotlin\nexpect fun platform(): String\nexpect fun currentTimeSeconds(): Long\n```\n\n**See** [references/expect-actual-catalog.md](references/expect-actual-catalog.md) for complete catalog with rationale.\n\n## Target-Specific Guidance\n\n### Android, JVM (Desktop), iOS - Current Primary Targets\n\n**Status:** Mature patterns, stable APIs\n\n**Android (androidMain):**\n- Uses Android framework (Activity, Context, etc.)\n- secp256k1-kmp-jni-android for crypto\n- AndroidX libraries\n\n**Desktop JVM (jvmMain):**\n- Uses Compose Desktop (Window, MenuBar, etc.)\n- secp256k1-kmp-jni-jvm for crypto\n- Pure JVM libraries\n\n**iOS (iosMain):**\n- Active development, framework configured\n- Architecture targets: iosX64Main, iosArm64Main, iosSimulatorArm64Main\n- Platform APIs via platform.posix, Security framework\n\n### Web, wasm - Future Targets\n\n**Status:** Not yet implemented, consider for future-proofing\n\n**Constraints to know:**\n- ❌ No platform.posix (file I/O different)\n- ❌ No JVM libraries (Jackson, OkHttp won't work)\n- ❌ Different async model (JS event loop vs threads)\n\n**Future-proofing tips:**\n1. Prefer pure Kotlin in commonMain\n2. Use kotlinx.* libraries:\n   - kotlinx.serialization instead of Jackson\n   - ktor instead of OkHttp (ktor supports web)\n   - kotlinx.datetime instead of custom date handling\n3. Avoid platform.posix for file operations\n4. Test abstractions work without JVM assumptions\n\n**Example migration path:**\n```kotlin\n// Current: jvmAndroid (JVM-only)\napi(libs.jackson.module.kotlin)\n\n// Future: commonMain (all platforms)\napi(libs.kotlinx.serialization.json)\n```\n\n## Integration: When to Invoke Other Skills\n\n### Invoke gradle-expert\n\nTrigger gradle-expert skill when encountering:\n- Dependency conflicts (e.g., secp256k1-android vs secp256k1-jvm version mismatch)\n- Build errors related to source sets\n- Version catalog issues (libs.versions.toml)\n- \"Duplicate class\" errors\n- Performance/build time issues\n\n**Example trigger:**\n```\nError: Duplicate class found: fr.acinq.secp256k1.Secp256k1\n```\n→ Invoke gradle-expert for dependency conflict resolution.\n\n### Flags to Raise\n\n**Platform code in commonMain:**\n```kotlin\n// ❌ INCORRECT - Android API in commonMain\nexpect fun getContext(): Context  // Context is Android-only!\n```\n→ Flag: \"Android API in commonMain won't compile on other platforms\"\n\n**Duplicated business logic:**\n```kotlin\n// ❌ INCORRECT - Same logic in both\n// androidMain/.../CryptoUtils.kt\nfun validateSignature(...) { ... }\n\n// jvmMain/.../CryptoUtils.kt\nfun validateSignature(...) { ... }  // Duplicated!\n```\n→ Flag: \"Business logic duplicated, should be in commonMain or expect/actual\"\n\n**Reinventing wheel - suggest KMP alternatives:**\n- Custom date/time → kotlinx.datetime\n- OkHttp → ktor (supports web)\n- Jackson → kotlinx.serialization\n- Custom UUID → kotlinx.uuid (when stable)\n\n## Common Pitfalls\n\n### 1. Over-Abstraction\n**Problem:** Creating expect/actual for UI components\n```kotlin\n// ❌ BAD\nexpect fun NavigationComponent(...)\n```\n**Why:** Navigation paradigms too different (Activity vs Window)\n**Fix:** Keep platform-specific, accept duplication\n\n### 2. Under-Sharing\n**Problem:** Duplicating business logic across platforms\n```kotlin\n// ❌ BAD - duplicated in androidMain and jvmMain\nfun parseNostrEvent(json: String): Event { ... }\n```\n**Why:** Bug fixes need to be applied twice, tests duplicated\n**Fix:** Move to commonMain (pure Kotlin) or create expect/actual\n\n### 3. Leaky Abstractions\n**Problem:** Platform code in commonMain\n```kotlin\n// commonMain - ❌ BAD\nimport android.content.Context  // Won't compile on iOS!\n```\n**Fix:** Use expect/actual or dependency injection\n\n### 4. Premature Abstraction\n**Problem:** Creating expect/actual before second platform needs it\n```kotlin\n// ❌ BAD - only used on Android currently\nexpect fun showNotification(...)\n```\n**Why:** Wrong abstraction boundaries, wasted effort\n**Fix:** Wait until iOS actually needs it, then abstract\n\n### 5. Wrong Source Set\n**Problem:** JVM libraries in commonMain\n```kotlin\n// commonMain - ❌ BAD\nimport com.fasterxml.jackson.databind.ObjectMapper\n```\n**Why:** Jackson won't compile on iOS/web\n**Fix:** Move to jvmAndroid or migrate to kotlinx.serialization\n\n## Quick Reference\n\n| Code Type | Recommended Location | Reason |\n|-----------|---------------------|--------|\n| Pure Kotlin business logic | commonMain | Works everywhere |\n| Nostr protocol, NIPs | commonMain | Core logic, no platform APIs |\n| JVM libs (Jackson, OkHttp) | jvmAndroid | Android + Desktop only |\n| Crypto (varies by platform) | expect in commonMain, actual in platforms | Different security APIs per platform |\n| I/O, logging | expect in commonMain, actual in platforms | Platform implementations differ |\n| State (business logic) | commonMain or commons/jvmAndroid | Reusable StateFlow patterns |\n| **ViewModels** | **commons/commonMain/viewmodels/** | **StateFlow/SharedFlow + logic shareable, Compose MP lifecycle compatible** |\n| UI formatters (pure) | commons/commonMain | Reusable, no dependencies |\n| UI components (simple) | commons/commonMain | Cards, buttons, dialogs |\n| **Screen layouts** | **Platform-specific** | **Window vs Activity, sidebar vs bottom nav** |\n| Navigation | Platform-specific only | Activity vs Window too different |\n| Permissions | Platform-specific only | APIs incompatible |\n| Platform UX (menus, etc.) | Platform-specific only | Native feel required |\n\n## See Also\n\n- [references/abstraction-examples.md](references/abstraction-examples.md) - Good/bad abstraction examples with rationale\n- [references/source-set-hierarchy.md](references/source-set-hierarchy.md) - Visual hierarchy with Amethyst examples\n- [references/expect-actual-catalog.md](references/expect-actual-catalog.md) - All 24 expect/actual pairs with \"why abstracted\"\n- [references/target-compatibility.md](references/target-compatibility.md) - Platform constraints and future-proofing\n\n## Scripts\n\n- `scripts/validate-kmp-structure.sh` - Detect incorrect placements, validate source sets\n- `scripts/suggest-kmp-dependency.sh` - Suggest KMP library alternatives (ktor, kotlinx.serialization, etc.)\n","frontmatter":{"name":"kotlin-multiplatform","description":"Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific,\nsource set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets\n(Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues.\nTriggers on: abstraction decisions (\"should I share this?\"), source set placement questions, expect/actual creation,\nbuild.gradle.kts work, incorrect placement detection, KMP dependency suggestions.\n"},"isInternal":false,"tokens":3318,"sizeBytes":14459},{"name":"SKILL.md","path":".claude/skills/kotlin-concurrency-expert/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kotlin-concurrency-expert/SKILL.md","title":"kotlin-concurrency-expert","category":"anthropic-skill","format":"markdown","content":"---\nname: kotlin-concurrency-expert\ndescription: Kotlin Coroutines review and remediation for Android. Use when asked to review concurrency usage, fix coroutine-related bugs, improve thread safety, or resolve lifecycle issues in Kotlin/Android code.\n---\n\n# Kotlin Concurrency Expert\n\n## Overview\n\nReview and fix Kotlin Coroutines issues in Android codebases by applying structured concurrency, lifecycle safety, proper scoping, and modern best practices with minimal behavior changes.\n\n## Workflow\n\n### 1. Triage the Issue\n\n- Capture the exact error, crash, or symptom (ANR, memory leak, race condition, incorrect state).\n- Check project coroutines setup: `kotlinx-coroutines-android` version, `lifecycle-runtime-ktx` version.\n- Identify the current scope context (`viewModelScope`, `lifecycleScope`, custom scope, or none).\n- Confirm whether the code is UI-bound (`Dispatchers.Main`) or intended to run off the main thread (`Dispatchers.IO`, `Dispatchers.Default`).\n- Verify Dispatcher injection patterns for testability.\n\n### 2. Apply the Smallest Safe Fix\n\nPrefer edits that preserve existing behavior while satisfying structured concurrency and lifecycle safety.\n\nCommon fixes:\n\n- **ANR / Main thread blocking**: Move heavy work to `withContext(Dispatchers.IO)` or `Dispatchers.Default`; ensure suspend functions are main-safe.\n- **Memory leaks / zombie coroutines**: Replace `GlobalScope` with a lifecycle-bound scope (`viewModelScope`, `lifecycleScope`, or injected `applicationScope`).\n- **Lifecycle collection issues**: Replace deprecated `launchWhenStarted` with `repeatOnLifecycle(Lifecycle.State.STARTED)`.\n- **State exposure**: Encapsulate `MutableStateFlow` / `MutableSharedFlow`; expose read-only `StateFlow` or `Flow`.\n- **CancellationException swallowing**: Ensure generic `catch (e: Exception)` blocks rethrow `CancellationException`.\n- **Non-cooperative cancellation**: Add `ensureActive()` or `yield()` in tight loops for cooperative cancellation.\n- **Callback APIs**: Convert listeners to `callbackFlow` with proper `awaitClose` cleanup.\n- **Hardcoded Dispatchers**: Inject `CoroutineDispatcher` via constructor for testability.\n\n## Critical Rules\n\n### Dispatcher Injection (Testability)\n\n```kotlin\n// CORRECT: Inject dispatcher\nclass UserRepository(\n    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO\n) {\n    suspend fun fetchUser() = withContext(ioDispatcher) { ... }\n}\n\n// INCORRECT: Hardcoded dispatcher\nclass UserRepository {\n    suspend fun fetchUser() = withContext(Dispatchers.IO) { ... }\n}\n```\n\n### Lifecycle-Aware Collection\n\n```kotlin\n// CORRECT: Use repeatOnLifecycle\nviewLifecycleOwner.lifecycleScope.launch {\n    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {\n        viewModel.uiState.collect { state -> updateUI(state) }\n    }\n}\n\n// INCORRECT: Direct collection (unsafe, deprecated)\nlifecycleScope.launchWhenStarted {\n    viewModel.uiState.collect { state -> updateUI(state) }\n}\n```\n\n### State Encapsulation\n\n```kotlin\n// CORRECT: Expose read-only StateFlow\nclass MyViewModel : ViewModel() {\n    private val _uiState = MutableStateFlow(UiState())\n    val uiState: StateFlow<UiState> = _uiState.asStateFlow()\n}\n\n// INCORRECT: Exposed mutable state\nclass MyViewModel : ViewModel() {\n    val uiState = MutableStateFlow(UiState()) // Leaks mutability\n}\n```\n\n### Exception Handling\n\n```kotlin\n// CORRECT: Rethrow CancellationException\ntry {\n    doSuspendWork()\n} catch (e: CancellationException) {\n    throw e // Must rethrow!\n} catch (e: Exception) {\n    handleError(e)\n}\n\n// INCORRECT: Swallows cancellation\ntry {\n    doSuspendWork()\n} catch (e: Exception) {\n    handleError(e) // CancellationException swallowed!\n}\n```\n\n### Cooperative Cancellation\n\n```kotlin\n// CORRECT: Check for cancellation in tight loops\nsuspend fun processLargeList(items: List<Item>) {\n    items.forEach { item ->\n        ensureActive() // Check cancellation\n        processItem(item)\n    }\n}\n\n// INCORRECT: Non-cooperative (ignores cancellation)\nsuspend fun processLargeList(items: List<Item>) {\n    items.forEach { item ->\n        processItem(item) // Never checks cancellation\n    }\n}\n```\n\n### Callback Conversion\n\n```kotlin\n// CORRECT: callbackFlow with awaitClose\nfun locationUpdates(): Flow<Location> = callbackFlow {\n    val listener = LocationListener { location ->\n        trySend(location)\n    }\n    locationManager.requestLocationUpdates(listener)\n    \n    awaitClose { locationManager.removeUpdates(listener) }\n}\n```\n\n## Scope Guidelines\n\n| Scope | Use When | Lifecycle |\n|-------|----------|-----------|\n| `viewModelScope` | ViewModel operations | Cleared with ViewModel |\n| `lifecycleScope` | UI operations in Activity/Fragment | Destroyed with lifecycle owner |\n| `repeatOnLifecycle` | Flow collection in UI | Started/Stopped with lifecycle state |\n| `applicationScope` (injected) | App-wide background work | Application lifetime |\n| `GlobalScope` | **NEVER USE** | Breaks structured concurrency |\n\n## Testing Pattern\n\n```kotlin\n@Test\nfun `loading data updates state`() = runTest {\n    val testDispatcher = StandardTestDispatcher(testScheduler)\n    val repository = FakeRepository()\n    val viewModel = MyViewModel(repository, testDispatcher)\n    \n    viewModel.loadData()\n    advanceUntilIdle()\n    \n    assertEquals(UiState.Success(data), viewModel.uiState.value)\n}\n```\n\n## Reference Material\n\n- [Kotlin Coroutines Best Practices](https://developer.android.com/kotlin/coroutines/coroutines-best-practices)\n- [StateFlow and SharedFlow](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow)\n- [repeatOnLifecycle API](https://developer.android.com/topic/libraries/architecture/coroutines#repeatOnLifecycle)\n","frontmatter":{"name":"kotlin-concurrency-expert","description":"Kotlin Coroutines review and remediation for Android. Use when asked to review concurrency usage, fix coroutine-related bugs, improve thread safety, or resolve lifecycle issues in Kotlin/Android code."},"isInternal":false,"tokens":1249,"sizeBytes":5677},{"name":"ideas-by-category.md","path":".claude/skills/marketing-ideas/references/ideas-by-category.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/marketing-ideas/references/ideas-by-category.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/marketing-ideas/references/ideas-by-category.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/marketing-ideas/references/ideas-by-category.md)","tokens":300,"sizeBytes":245},{"name":"SKILL.md","path":".claude/skills/marketing-ideas/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/marketing-ideas/SKILL.md","title":"marketing-ideas","category":"anthropic-skill","format":"markdown","content":"---\nname: marketing-ideas\nversion: 1.0.0\ndescription: \"When the user needs marketing ideas, inspiration, or strategies for their SaaS or software product. Also use when the user asks for 'marketing ideas,' 'growth ideas,' 'how to market,' 'marketing strategies,' 'marketing tactics,' 'ways to promote,' or 'ideas to grow.' This skill provides 139 proven marketing approaches organized by category.\"\n---\n\n# Marketing Ideas for SaaS\n\nYou are a marketing strategist with a library of 139 proven marketing ideas. Your goal is to help users find the right marketing strategies for their specific situation, stage, and resources.\n\n## How to Use This Skill\n\n**Check for product marketing context first:**\nIf `.claude/product-marketing-context.md` exists, read it before asking questions. Use that context and only ask for information not already covered or specific to this task.\n\nWhen asked for marketing ideas:\n1. Ask about their product, audience, and current stage if not clear\n2. Suggest 3-5 most relevant ideas based on their context\n3. Provide details on implementation for chosen ideas\n4. Consider their resources (time, budget, team size)\n\n---\n\n## Ideas by Category (Quick Reference)\n\n| Category | Ideas | Examples |\n|----------|-------|----------|\n| Content & SEO | 1-10 | Programmatic SEO, Glossary marketing, Content repurposing |\n| Competitor | 11-13 | Comparison pages, Marketing jiu-jitsu |\n| Free Tools | 14-22 | Calculators, Generators, Chrome extensions |\n| Paid Ads | 23-34 | LinkedIn, Google, Retargeting, Podcast ads |\n| Social & Community | 35-44 | LinkedIn audience, Reddit marketing, Short-form video |\n| Email | 45-53 | Founder emails, Onboarding sequences, Win-back |\n| Partnerships | 54-64 | Affiliate programs, Integration marketing, Newsletter swaps |\n| Events | 65-72 | Webinars, Conference speaking, Virtual summits |\n| PR & Media | 73-76 | Press coverage, Documentaries |\n| Launches | 77-86 | Product Hunt, Lifetime deals, Giveaways |\n| Product-Led | 87-96 | Viral loops, Powered-by marketing, Free migrations |\n| Content Formats | 97-109 | Podcasts, Courses, Annual reports, Year wraps |\n| Unconventional | 110-122 | Awards, Challenges, Guerrilla marketing |\n| Platforms | 123-130 | App marketplaces, Review sites, YouTube |\n| International | 131-132 | Expansion, Price localization |\n| Developer | 133-136 | DevRel, Certifications |\n| Audience-Specific | 137-139 | Referrals, Podcast tours, Customer language |\n\n**For the complete list with descriptions**: See [references/ideas-by-category.md](references/ideas-by-category.md)\n\n---\n\n## Implementation Tips\n\n### By Stage\n\n**Pre-launch:**\n- Waitlist referrals (#79)\n- Early access pricing (#81)\n- Product Hunt prep (#78)\n\n**Early stage:**\n- Content & SEO (#1-10)\n- Community (#35)\n- Founder-led sales (#47)\n\n**Growth stage:**\n- Paid acquisition (#23-34)\n- Partnerships (#54-64)\n- Events (#65-72)\n\n**Scale:**\n- Brand campaigns\n- International (#131-132)\n- Media acquisitions (#73)\n\n### By Budget\n\n**Free:**\n- Content & SEO\n- Community building\n- Social media\n- Comment marketing\n\n**Low budget:**\n- Targeted ads\n- Sponsorships\n- Free tools\n\n**Medium budget:**\n- Events\n- Partnerships\n- PR\n\n**High budget:**\n- Acquisitions\n- Conferences\n- Brand campaigns\n\n### By Timeline\n\n**Quick wins:**\n- Ads, email, social posts\n\n**Medium-term:**\n- Content, SEO, community\n\n**Long-term:**\n- Brand, thought leadership, platform effects\n\n---\n\n## Top Ideas by Use Case\n\n### Need Leads Fast\n- Google Ads (#31) - High-intent search\n- LinkedIn Ads (#28) - B2B targeting\n- Engineering as Marketing (#15) - Free tool lead gen\n\n### Building Authority\n- Conference Speaking (#70)\n- Book Marketing (#104)\n- Podcasts (#107)\n\n### Low Budget Growth\n- Easy Keyword Ranking (#1)\n- Reddit Marketing (#38)\n- Comment Marketing (#44)\n\n### Product-Led Growth\n- Viral Loops (#93)\n- Powered By Marketing (#87)\n- In-App Upsells (#91)\n\n### Enterprise Sales\n- Investor Marketing (#133)\n- Expert Networks (#57)\n- Conference Sponsorship (#72)\n\n---\n\n## Output Format\n\nWhen recommending ideas, provide for each:\n\n- **Idea name**: One-line description\n- **Why it fits**: Connection to their situation\n- **How to start**: First 2-3 implementation steps\n- **Expected outcome**: What success looks like\n- **Resources needed**: Time, budget, skills required\n\n---\n\n## Task-Specific Questions\n\n1. What's your current stage and main growth goal?\n2. What's your marketing budget and team size?\n3. What have you already tried that worked or didn't?\n4. What competitor tactics do you admire?\n\n---\n\n## Related Skills\n\n- **programmatic-seo**: For scaling SEO content (#4)\n- **competitor-alternatives**: For comparison pages (#11)\n- **email-sequence**: For email marketing tactics\n- **free-tool-strategy**: For engineering as marketing (#15)\n- **referral-program**: For viral growth (#93)\n","frontmatter":{"name":"marketing-ideas","version":"1.0.0","description":"When the user needs marketing ideas, inspiration, or strategies for their SaaS or software product. Also use when the user asks for 'marketing ideas,' 'growth ideas,' 'how to market,' 'marketing strategies,' 'marketing tactics,' 'ways to promote,' or 'ideas to grow.' This skill provides 139 proven marketing approaches organized by category."},"isInternal":false,"tokens":1171,"sizeBytes":4805},{"name":"android-navigation.md","path":".claude/skills/mobile-android-design/references/android-navigation.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/android-navigation.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/mobile-android-design/references/android-navigation.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/android-navigation.md)","tokens":300,"sizeBytes":259},{"name":"compose-components.md","path":".claude/skills/mobile-android-design/references/compose-components.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/compose-components.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/mobile-android-design/references/compose-components.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/compose-components.md)","tokens":300,"sizeBytes":259},{"name":"material3-theming.md","path":".claude/skills/mobile-android-design/references/material3-theming.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/material3-theming.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/mobile-android-design/references/material3-theming.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/material3-theming.md)","tokens":300,"sizeBytes":257},{"name":"SKILL.md","path":".claude/skills/mobile-android-design/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/SKILL.md","title":"mobile-android-design","category":"anthropic-skill","format":"markdown","content":"---\nname: mobile-android-design\ndescription: Master Material Design 3 and Jetpack Compose patterns for building native Android apps. Use when designing Android interfaces, implementing Compose UI, or following Google's Material Design guidelines.\n---\n\n# Android Mobile Design\n\nMaster Material Design 3 (Material You) and Jetpack Compose to build modern, adaptive Android applications that integrate seamlessly with the Android ecosystem.\n\n## When to Use This Skill\n\n- Designing Android app interfaces following Material Design 3\n- Building Jetpack Compose UI and layouts\n- Implementing Android navigation patterns (Navigation Compose)\n- Creating adaptive layouts for phones, tablets, and foldables\n- Using Material 3 theming with dynamic colors\n- Building accessible Android interfaces\n- Implementing Android-specific gestures and interactions\n- Designing for different screen configurations\n\n## Core Concepts\n\n### 1. Material Design 3 Principles\n\n**Personalization**: Dynamic color adapts UI to user's wallpaper\n**Accessibility**: Tonal palettes ensure sufficient color contrast\n**Large Screens**: Responsive layouts for tablets and foldables\n\n**Material Components:**\n\n- Cards, Buttons, FABs, Chips\n- Navigation (rail, drawer, bottom nav)\n- Text fields, Dialogs, Sheets\n- Lists, Menus, Progress indicators\n\n### 2. Jetpack Compose Layout System\n\n**Column and Row:**\n\n```kotlin\n// Vertical arrangement with alignment\nColumn(\n    modifier = Modifier.padding(16.dp),\n    verticalArrangement = Arrangement.spacedBy(12.dp),\n    horizontalAlignment = Alignment.Start\n) {\n    Text(\n        text = \"Title\",\n        style = MaterialTheme.typography.headlineSmall\n    )\n    Text(\n        text = \"Subtitle\",\n        style = MaterialTheme.typography.bodyMedium,\n        color = MaterialTheme.colorScheme.onSurfaceVariant\n    )\n}\n\n// Horizontal arrangement with weight\nRow(\n    modifier = Modifier.fillMaxWidth(),\n    horizontalArrangement = Arrangement.SpaceBetween,\n    verticalAlignment = Alignment.CenterVertically\n) {\n    Icon(Icons.Default.Star, contentDescription = null)\n    Text(\"Featured\")\n    Spacer(modifier = Modifier.weight(1f))\n    TextButton(onClick = {}) {\n        Text(\"View All\")\n    }\n}\n```\n\n**Lazy Lists and Grids:**\n\n```kotlin\n// Lazy column with sticky headers\nLazyColumn {\n    items.groupBy { it.category }.forEach { (category, categoryItems) ->\n        stickyHeader {\n            Text(\n                text = category,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.surface)\n                    .padding(16.dp),\n                style = MaterialTheme.typography.titleMedium\n            )\n        }\n        items(categoryItems) { item ->\n            ItemRow(item = item)\n        }\n    }\n}\n\n// Adaptive grid\nLazyVerticalGrid(\n    columns = GridCells.Adaptive(minSize = 150.dp),\n    contentPadding = PaddingValues(16.dp),\n    horizontalArrangement = Arrangement.spacedBy(12.dp),\n    verticalArrangement = Arrangement.spacedBy(12.dp)\n) {\n    items(items) { item ->\n        ItemCard(item = item)\n    }\n}\n```\n\n### 3. Navigation Patterns\n\n**Bottom Navigation:**\n\n```kotlin\n@Composable\nfun MainScreen() {\n    val navController = rememberNavController()\n\n    Scaffold(\n        bottomBar = {\n            NavigationBar {\n                val navBackStackEntry by navController.currentBackStackEntryAsState()\n                val currentDestination = navBackStackEntry?.destination\n\n                NavigationDestination.entries.forEach { destination ->\n                    NavigationBarItem(\n                        icon = { Icon(destination.icon, contentDescription = null) },\n                        label = { Text(destination.label) },\n                        selected = currentDestination?.hierarchy?.any {\n                            it.route == destination.route\n                        } == true,\n                        onClick = {\n                            navController.navigate(destination.route) {\n                                popUpTo(navController.graph.findStartDestination().id) {\n                                    saveState = true\n                                }\n                                launchSingleTop = true\n                                restoreState = true\n                            }\n                        }\n                    )\n                }\n            }\n        }\n    ) { innerPadding ->\n        NavHost(\n            navController = navController,\n            startDestination = NavigationDestination.Home.route,\n            modifier = Modifier.padding(innerPadding)\n        ) {\n            composable(NavigationDestination.Home.route) { HomeScreen() }\n            composable(NavigationDestination.Search.route) { SearchScreen() }\n            composable(NavigationDestination.Profile.route) { ProfileScreen() }\n        }\n    }\n}\n```\n\n**Navigation Drawer:**\n\n```kotlin\n@Composable\nfun DrawerNavigation() {\n    val drawerState = rememberDrawerState(DrawerValue.Closed)\n    val scope = rememberCoroutineScope()\n\n    ModalNavigationDrawer(\n        drawerState = drawerState,\n        drawerContent = {\n            ModalDrawerSheet {\n                Spacer(Modifier.height(12.dp))\n                Text(\n                    \"App Name\",\n                    modifier = Modifier.padding(16.dp),\n                    style = MaterialTheme.typography.titleLarge\n                )\n                HorizontalDivider()\n\n                NavigationDrawerItem(\n                    icon = { Icon(Icons.Default.Home, null) },\n                    label = { Text(\"Home\") },\n                    selected = true,\n                    onClick = { scope.launch { drawerState.close() } }\n                )\n                NavigationDrawerItem(\n                    icon = { Icon(Icons.Default.Settings, null) },\n                    label = { Text(\"Settings\") },\n                    selected = false,\n                    onClick = { }\n                )\n            }\n        }\n    ) {\n        Scaffold(\n            topBar = {\n                TopAppBar(\n                    title = { Text(\"Home\") },\n                    navigationIcon = {\n                        IconButton(onClick = { scope.launch { drawerState.open() } }) {\n                            Icon(Icons.Default.Menu, contentDescription = \"Menu\")\n                        }\n                    }\n                )\n            }\n        ) { innerPadding ->\n            Content(modifier = Modifier.padding(innerPadding))\n        }\n    }\n}\n```\n\n### 4. Material 3 Theming\n\n**Color Scheme:**\n\n```kotlin\n// Dynamic color (Android 12+)\nval dynamicColorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n    val context = LocalContext.current\n    if (darkTheme) dynamicDarkColorScheme(context)\n    else dynamicLightColorScheme(context)\n} else {\n    if (darkTheme) DarkColorScheme else LightColorScheme\n}\n\n// Custom color scheme\nprivate val LightColorScheme = lightColorScheme(\n    primary = Color(0xFF6750A4),\n    onPrimary = Color.White,\n    primaryContainer = Color(0xFFEADDFF),\n    onPrimaryContainer = Color(0xFF21005D),\n    secondary = Color(0xFF625B71),\n    onSecondary = Color.White,\n    tertiary = Color(0xFF7D5260),\n    onTertiary = Color.White,\n    surface = Color(0xFFFFFBFE),\n    onSurface = Color(0xFF1C1B1F),\n)\n```\n\n**Typography:**\n\n```kotlin\nval AppTypography = Typography(\n    displayLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 57.sp,\n        lineHeight = 64.sp\n    ),\n    headlineMedium = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 28.sp,\n        lineHeight = 36.sp\n    ),\n    titleLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 22.sp,\n        lineHeight = 28.sp\n    ),\n    bodyLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 16.sp,\n        lineHeight = 24.sp\n    ),\n    labelMedium = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Medium,\n        fontSize = 12.sp,\n        lineHeight = 16.sp\n    )\n)\n```\n\n### 5. Component Examples\n\n**Cards:**\n\n```kotlin\n@Composable\nfun FeatureCard(\n    title: String,\n    description: String,\n    imageUrl: String,\n    onClick: () -> Unit\n) {\n    Card(\n        onClick = onClick,\n        modifier = Modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(16.dp),\n        colors = CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surfaceVariant\n        )\n    ) {\n        Column {\n            AsyncImage(\n                model = imageUrl,\n                contentDescription = null,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .height(180.dp),\n                contentScale = ContentScale.Crop\n            )\n            Column(modifier = Modifier.padding(16.dp)) {\n                Text(\n                    text = title,\n                    style = MaterialTheme.typography.titleMedium\n                )\n                Spacer(modifier = Modifier.height(8.dp))\n                Text(\n                    text = description,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            }\n        }\n    }\n}\n```\n\n**Buttons:**\n\n```kotlin\n// Filled button (primary action)\nButton(onClick = { }) {\n    Text(\"Continue\")\n}\n\n// Filled tonal button (secondary action)\nFilledTonalButton(onClick = { }) {\n    Icon(Icons.Default.Add, null)\n    Spacer(Modifier.width(8.dp))\n    Text(\"Add Item\")\n}\n\n// Outlined button\nOutlinedButton(onClick = { }) {\n    Text(\"Cancel\")\n}\n\n// Text button\nTextButton(onClick = { }) {\n    Text(\"Learn More\")\n}\n\n// FAB\nFloatingActionButton(\n    onClick = { },\n    containerColor = MaterialTheme.colorScheme.primaryContainer,\n    contentColor = MaterialTheme.colorScheme.onPrimaryContainer\n) {\n    Icon(Icons.Default.Add, contentDescription = \"Add\")\n}\n```\n\n## Quick Start Component\n\n```kotlin\n@Composable\nfun ItemListCard(\n    item: Item,\n    onItemClick: () -> Unit,\n    modifier: Modifier = Modifier\n) {\n    Card(\n        onClick = onItemClick,\n        modifier = modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(12.dp)\n    ) {\n        Row(\n            modifier = Modifier\n                .padding(16.dp)\n                .fillMaxWidth(),\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            Box(\n                modifier = Modifier\n                    .size(48.dp)\n                    .clip(CircleShape)\n                    .background(MaterialTheme.colorScheme.primaryContainer),\n                contentAlignment = Alignment.Center\n            ) {\n                Icon(\n                    imageVector = Icons.Default.Star,\n                    contentDescription = null,\n                    tint = MaterialTheme.colorScheme.onPrimaryContainer\n                )\n            }\n\n            Spacer(modifier = Modifier.width(16.dp))\n\n            Column(modifier = Modifier.weight(1f)) {\n                Text(\n                    text = item.title,\n                    style = MaterialTheme.typography.titleMedium\n                )\n                Text(\n                    text = item.subtitle,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            }\n\n            Icon(\n                imageVector = Icons.Default.ChevronRight,\n                contentDescription = null,\n                tint = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n    }\n}\n```\n\n## Best Practices\n\n1. **Use Material Theme**: Access colors via `MaterialTheme.colorScheme` for automatic dark mode support\n2. **Support Dynamic Color**: Enable dynamic color on Android 12+ for personalization\n3. **Adaptive Layouts**: Use `WindowSizeClass` for responsive designs\n4. **Content Descriptions**: Add `contentDescription` to all interactive elements\n5. **Touch Targets**: Minimum 48dp touch targets for accessibility\n6. **State Hoisting**: Hoist state to make components reusable and testable\n7. **Remember Properly**: Use `remember` and `rememberSaveable` appropriately\n8. **Preview Annotations**: Add `@Preview` with different configurations\n\n## Common Issues\n\n- **Recomposition Issues**: Avoid passing unstable lambdas; use `remember`\n- **State Loss**: Use `rememberSaveable` for configuration changes\n- **Performance**: Use `LazyColumn` instead of `Column` for long lists\n- **Theme Leaks**: Ensure `MaterialTheme` wraps all composables\n- **Navigation Crashes**: Handle back press and deep links properly\n- **Memory Leaks**: Cancel coroutines in `DisposableEffect`\n\n## Resources\n\n- [Material Design 3](https://m3.material.io/)\n- [Jetpack Compose Documentation](https://developer.android.com/jetpack/compose)\n- [Compose Samples](https://github.com/android/compose-samples)\n- [Material 3 Compose](https://developer.android.com/jetpack/compose/designsystems/material3)\n","frontmatter":{"name":"mobile-android-design","description":"Master Material Design 3 and Jetpack Compose patterns for building native Android apps. Use when designing Android interfaces, implementing Compose UI, or following Google's Material Design guidelines."},"isInternal":false,"tokens":2619,"sizeBytes":13144},{"name":"SKILL.md","path":".claude/skills/simpmusic-icons/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/simpmusic-icons/SKILL.md","title":"simpmusic-icons","category":"anthropic-skill","format":"markdown","content":"---\nname: simpmusic-icons\ndescription: Add or change an icon in SimpMusic. Every icon is a generated Material Symbols ImageVector under ui/icon, addressed as SimpIcons.<Name> — there is no material-icons dependency and no XML icon drawable. Use when adding a new icon, replacing one, or hitting ImageVector/Painter type errors.\n---\n\n# SimpMusic Icons\n\nIcons live in `composeApp/src/commonMain/kotlin/com/maxrave/simpmusic/ui/icon/`, one file per\nicon, each declaring `val SimpIcons.<Name>: ImageVector`.\n\nTwo things are deliberately absent and must not come back:\n- the `material-icons-core` / `material-icons-extended` dependency (`Icons.Rounded.*`)\n- XML icon drawables in `composeResources/drawable` used through `painterResource(...)`\n\n## Adding an icon\n\nGoogle's font service generates the Compose file directly — no conversion tool needed:\n\n```bash\ncurl -sfL --compressed \\\n  \"https://fonts.gstatic.com/render/v1/Material+Symbols+Rounded/24dp/<symbol_name>.kt?var=opsz,wght,FILL,GRAD,ROND@24,400,1,0,50\" \\\n  -o <PascalName>.kt\n```\n\nKeep these axes identical for every icon, or the set stops looking like one set:\n**Rounded, opsz 24, wght 400, GRAD 0, ROND 50**, `FILL=1`.\n\nUse `FILL=0` **only** for the \"off\" half of a state pair — `FavoriteBorder`,\n`AddCircleOutline`, `DownloadForOfflineOutlined`. At `FILL=1` the off state renders identically to\nthe on state and the distinction is lost.\n\nThen edit the downloaded file:\n\n1. `package com.example.test` → `package com.maxrave.simpmusic.ui.icon`\n2. `public val <symbol_name>: ImageVector` → `val SimpIcons.<PascalName>: ImageVector`\n3. Rename the backing field `_<symbol_name>` → `_<PascalName>`, and `name = \"<symbol_name>\"` → `\"<PascalName>\"`\n4. Never add `autoMirror = true` to `ImageVector.Builder(...)`: in right-to-left languages only text\n   flips, icons keep their shape (owner's rule, 2026-10-14). `ArrowForwardIos`, `QueueMusic`, `Sort`,\n   `TrendingUp`, `VolumeOff` and `VolumeUp` had it and drew mirrored until it was removed.\n5. Replace the closing `.build()` with `.buildSimpIcon()` (in `SimpIcons.kt`): it is `build()` plus\n   the fit into Material's 20×20 live area described under Traps. `SimpIconsFitTest` fails on a\n   file that still ends with `.build()`.\n\n## Using an icon\n\n```kotlin\nimport com.maxrave.simpmusic.ui.icon.PlayArrow   // per-icon import, required\nimport com.maxrave.simpmusic.ui.icon.SimpIcons\n\nIcon(SimpIcons.PlayArrow, contentDescription = null)\n```\n\nThe project's own icon buttons take an `ImageVector`:\n`RippleIconButton(imageVector = …)`, `LiquidGlassIconButton(imageVector = …)`,\n`ActionButton(icon = …)`.\n\n## Traps\n\n**Every icon needs its own import.** `val SimpIcons.X` is an *extension property*; importing the\n`SimpIcons` object alone leaves it unresolved. This is the same mechanism that lets R8 strip unused\nicons, so never \"simplify\" the set into a `map` or a `when` — that would ship all of them.\n\n**`ImageVector` is not a `Painter`.** `Icon` and `Image` have overloads for both, so those look\nfine. These do not, and need `rememberVectorPainter(SimpIcons.X)`:\n- `AsyncImage(placeholder = …, error = …)`\n- anything drawn inside a `DrawScope` (`with(painter) { draw(...) }`)\n- custom composables whose parameter is typed `Painter`\n\n**Do not text-replace blindly.** A regex swap of `Res.drawable.X` → `SimpIcons.X` produced 40\ncompile errors in one pass: it also hit `Painter` parameters, and it landed *inside* multi-line\n`painterResource(\\n  Res.drawable.X,\\n)` calls, leaving the syntactically valid but wrong\n`painterResource(SimpIcons.X)`.\n\n**Do not replace an icon whose colour carries meaning.** These stay as resources:\n\n| File | Why |\n|---|---|\n| `baseline_downloaded.xml` | `#FF00A0CB` — the blue *is* the \"downloaded\" state |\n| `baseline_favorite_24.xml` | `#D10000` — the red *is* the \"liked\" state |\n| `mono.xml`, `monochrome.xml` | SimpMusic logos (`#0AEAF8` / black) |\n| `app_icon.png`, `circle_app_icon.png`, `holder*.png` | bitmaps, not icons |\n\nIf a state needs that colour with a shared symbol, pass it explicitly — e.g. `ActionButton`'s\n`iconColor = Color(0xFF00A0CB)` for the downloaded state.\n\n**Verify the symbol name exists** before assuming a mapping, against\n`google/material-design-icons` → `variablefont/MaterialSymbolsRounded[FILL,GRAD,opsz,wght].codepoints`.\nLegacy names such as `favorite_border`, `add_circle_outline` and `thumb_up_alt` *do* still exist;\n`person_add_alt_1` does not (use `person_add`).\n\n**Glyphs that spill past Material's 20×20 live area are fitted into it (2026-10-18).** Material's\nicon grid keeps a glyph inside the centred 20×20 square of the 24 box, with 2dp of padding, and\nbalances the glyphs inside it by keyline shapes of about equal area. 24 of our 111 Symbols spill\ninto that padding anyway: `Lyrics`' note reaches the box edge, `ThumbsUpDown` spans the\nfull width. At one size those read bigger than everything beside them. `buildSimpIcon()` measures\nhow far each glyph reaches from the centre along either axis (walking its path nodes, 16 samples\nper curve) and shrinks one that passes 10 of 24 about the centre until it fits: `Lyrics` and\n`ThumbsUpDown` ×0.83, the other 21 ×0.86–0.996. A glyph inside the square is left exactly as Google\ndrew it, nothing is enlarged, and anything it cannot measure (an arc, a group, a fit tighter than\n×0.6) keeps the glyph as generated and logs under `SimpIcons`. `SimpIconsFitTest` (jvmTest) fails if\na file skips `buildSimpIcon()`, an icon outside `KEEP_AS_DRAWN` ends up outside the square, or one in\nit gets rebuilt.\n- `Groups` is named in `KEEP_AS_DRAWN` (in `SimpIcons.kt`) and keeps Google's drawing (2026-10-20).\n  It spills only sideways, so fitting its 24 width also cut its height, which was never too tall,\n  from 12 to 10 of 24, and it read shorter than every icon beside it. Five more spill only sideways\n  and lose a little height to the fit (`PeopleAlt`, `PersonAdd`, `Insights` 9%, `AutoGraph` 6%,\n  `AccessAlarm` 2%); they still end 14.5 of 24 tall or more, like Symbols Google draws at that\n  height, so they stay fitted (owner's call).\n- Do not fit glyphs into a circle instead. That was tried first: a circle judges a glyph by its one\n  farthest point, so `Lyrics` shrank to ×0.72 to pull its note's tip inside and then read far\n  smaller than `QueueMusic` beside it.\n\n**A small round button cuts a glyph that fills the live area.** `IconButton` clips its content to\nits `shape` (a circle by default), and a button sized 24dp makes that a 24dp circle, 12dp from the\ncentre. A glyph that fills the 20×20 square reaches 14.1 at its corners — `comment` reaches 13.3 —\nso its corners are cut while round or open glyphs beside it are not. The icon file is not the\nproblem; the button is. Check the farthest glyph point before putting an icon in one: Classic's\nComments button clips to 28dp with `requiredSize(28.dp)` inside the same 24dp slot.\n\n**Icons in one row take one size.** Tuning each glyph by eye is what made the Apple Music row read\nbig-small (23 and 24dp beside 22dp dock buttons, the title-row star 32dp beside 24dp glyphs). Only a\ndeliberate hierarchy, such as a Play button over Previous/Next, gets a different size.\n\n**The response is gzipped** even when the request asks for `identity` — `curl --compressed`, or\ndecompress by magic bytes (`\\x1f\\x8b`) when fetching from a script.\n","frontmatter":{"name":"simpmusic-icons","description":"Add or change an icon in SimpMusic. Every icon is a generated Material Symbols ImageVector under ui/icon, addressed as SimpIcons.<Name> — there is no material-icons dependency and no XML icon drawable. Use when adding a new icon, replacing one, or hitting ImageVector/Painter type errors."},"isInternal":false,"tokens":2026,"sizeBytes":7369},{"name":"SKILL.md","path":".claude/skills/xml-to-compose-migration/SKILL.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/xml-to-compose-migration/SKILL.md","title":"xml-to-compose-migration","category":"anthropic-skill","format":"markdown","content":"---\nname: xml-to-compose-migration\ndescription: Convert Android XML layouts to Jetpack Compose. Use when asked to migrate Views to Compose, convert XML to Composables, or modernize UI from View system to Compose.\n---\n\n# XML to Compose Migration\n\n## Overview\n\nSystematically convert Android XML layouts to idiomatic Jetpack Compose, preserving functionality while embracing Compose patterns. This skill covers layout mapping, state migration, and incremental adoption strategies.\n\n## Workflow\n\n### 1. Analyze the XML Layout\n\n- Identify the root layout type (`ConstraintLayout`, `LinearLayout`, `FrameLayout`, etc.).\n- List all View widgets and their key attributes.\n- Map data binding expressions (`@{}`) or view binding references.\n- Identify custom views that need special handling.\n- Note any `include`, `merge`, or `ViewStub` usage.\n\n### 2. Plan the Migration\n\n- Decide: **Full rewrite** or **incremental migration** (using `ComposeView`/`AndroidView`).\n- Identify state sources (ViewModel, LiveData, savedInstanceState).\n- List reusable components to extract as separate Composables.\n- Plan navigation integration if using Navigation component.\n\n### 3. Convert Layouts\n\nApply the layout mapping table below to convert each View to its Compose equivalent.\n\n### 4. Migrate State\n\n- Convert `LiveData` observation to `StateFlow` collection or `observeAsState()`.\n- Replace `findViewById` / ViewBinding with Compose state.\n- Convert click listeners to lambda parameters.\n\n### 5. Test and Verify\n\n- Compare visual output between XML and Compose versions.\n- Test accessibility (content descriptions, touch targets).\n- Verify state preservation across configuration changes.\n\n---\n\n## Layout Mapping Reference\n\n### Container Layouts\n\n| XML Layout | Compose Equivalent | Notes |\n|------------|-------------------|-------|\n| `LinearLayout (vertical)` | `Column` | Use `Arrangement` and `Alignment` |\n| `LinearLayout (horizontal)` | `Row` | Use `Arrangement` and `Alignment` |\n| `FrameLayout` | `Box` | Children stack on top of each other |\n| `ConstraintLayout` | `ConstraintLayout` (Compose) | Use `createRefs()` and `constrainAs` |\n| `RelativeLayout` | `Box` or `ConstraintLayout` | Prefer Box for simple overlap |\n| `ScrollView` | `Column` + `Modifier.verticalScroll()` | Or use `LazyColumn` for lists |\n| `HorizontalScrollView` | `Row` + `Modifier.horizontalScroll()` | Or use `LazyRow` for lists |\n| `RecyclerView` | `LazyColumn` / `LazyRow` / `LazyGrid` | Most common migration |\n| `ViewPager2` | `HorizontalPager` | From accompanist or Compose Foundation |\n| `CoordinatorLayout` | Custom + `Scaffold` | Use `TopAppBar` with scroll behavior |\n| `NestedScrollView` | `Column` + `Modifier.verticalScroll()` | Prefer Lazy variants |\n\n### Common Widgets\n\n| XML Widget | Compose Equivalent | Notes |\n|------------|-------------------|-------|\n| `TextView` | `Text` | Use `style` → `TextStyle` |\n| `EditText` | `TextField` / `OutlinedTextField` | Requires state hoisting |\n| `Button` | `Button` | Use `onClick` lambda |\n| `ImageView` | `Image` | Use `painterResource()` or Coil |\n| `ImageButton` | `IconButton` | Use `Icon` inside |\n| `CheckBox` | `Checkbox` | Requires `checked` + `onCheckedChange` |\n| `RadioButton` | `RadioButton` | Use with `Row` for groups |\n| `Switch` | `Switch` | Requires state hoisting |\n| `ProgressBar (circular)` | `CircularProgressIndicator` | |\n| `ProgressBar (horizontal)` | `LinearProgressIndicator` | |\n| `SeekBar` | `Slider` | Requires state hoisting |\n| `Spinner` | `DropdownMenu` + `ExposedDropdownMenuBox` | More complex pattern |\n| `CardView` | `Card` | From Material 3 |\n| `Toolbar` | `TopAppBar` | Use inside `Scaffold` |\n| `BottomNavigationView` | `NavigationBar` | Material 3 |\n| `FloatingActionButton` | `FloatingActionButton` | Use inside `Scaffold` |\n| `Divider` | `HorizontalDivider` / `VerticalDivider` | |\n| `Space` | `Spacer` | Use `Modifier.size()` |\n\n### Attribute Mapping\n\n| XML Attribute | Compose Modifier/Property |\n|---------------|--------------------------|\n| `android:layout_width=\"match_parent\"` | `Modifier.fillMaxWidth()` |\n| `android:layout_height=\"match_parent\"` | `Modifier.fillMaxHeight()` |\n| `android:layout_width=\"wrap_content\"` | `Modifier.wrapContentWidth()` (usually implicit) |\n| `android:layout_weight` | `Modifier.weight(1f)` |\n| `android:padding` | `Modifier.padding()` |\n| `android:layout_margin` | `Modifier.padding()` on parent, or use `Arrangement.spacedBy()` |\n| `android:background` | `Modifier.background()` |\n| `android:visibility=\"gone\"` | Conditional composition (don't emit) |\n| `android:visibility=\"invisible\"` | `Modifier.alpha(0f)` (keeps space) |\n| `android:clickable` | `Modifier.clickable { }` |\n| `android:contentDescription` | `Modifier.semantics { contentDescription = \"\" }` |\n| `android:elevation` | `Modifier.shadow()` or component's `elevation` param |\n| `android:alpha` | `Modifier.alpha()` |\n| `android:rotation` | `Modifier.rotate()` |\n| `android:scaleX/Y` | `Modifier.scale()` |\n| `android:gravity` | `Alignment` parameter or `Arrangement` |\n| `android:layout_gravity` | `Modifier.align()` |\n\n---\n\n## Common Patterns\n\n### LinearLayout with Weights\n\n```xml\n<!-- XML -->\n<LinearLayout android:orientation=\"horizontal\">\n    <View android:layout_weight=\"1\" />\n    <View android:layout_weight=\"2\" />\n</LinearLayout>\n```\n\n```kotlin\n// Compose\nRow(modifier = Modifier.fillMaxWidth()) {\n    Box(modifier = Modifier.weight(1f))\n    Box(modifier = Modifier.weight(2f))\n}\n```\n\n### RecyclerView to LazyColumn\n\n```xml\n<!-- XML -->\n<androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/recyclerView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />\n```\n\n```kotlin\n// Compose\nLazyColumn(modifier = Modifier.fillMaxSize()) {\n    items(items, key = { it.id }) { item ->\n        ItemRow(item = item, onClick = { onItemClick(item) })\n    }\n}\n```\n\n### EditText with Two-Way Binding\n\n```xml\n<!-- XML with Data Binding -->\n<EditText\n    android:text=\"@={viewModel.username}\"\n    android:hint=\"@string/username_hint\" />\n```\n\n```kotlin\n// Compose\nval username by viewModel.username.collectAsState()\n\nOutlinedTextField(\n    value = username,\n    onValueChange = { viewModel.updateUsername(it) },\n    label = { Text(stringResource(R.string.username_hint)) },\n    modifier = Modifier.fillMaxWidth()\n)\n```\n\n### ConstraintLayout Migration\n\n```xml\n<!-- XML -->\n<androidx.constraintlayout.widget.ConstraintLayout>\n    <TextView\n        android:id=\"@+id/title\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\" />\n    <TextView\n        android:id=\"@+id/subtitle\"\n        app:layout_constraintTop_toBottomOf=\"@id/title\"\n        app:layout_constraintStart_toStartOf=\"@id/title\" />\n</androidx.constraintlayout.widget.ConstraintLayout>\n```\n\n```kotlin\n// Compose\nConstraintLayout(modifier = Modifier.fillMaxWidth()) {\n    val (title, subtitle) = createRefs()\n    \n    Text(\n        text = \"Title\",\n        modifier = Modifier.constrainAs(title) {\n            top.linkTo(parent.top)\n            start.linkTo(parent.start)\n        }\n    )\n    Text(\n        text = \"Subtitle\", \n        modifier = Modifier.constrainAs(subtitle) {\n            top.linkTo(title.bottom)\n            start.linkTo(title.start)\n        }\n    )\n}\n```\n\n### Include / Merge → Extract Composable\n\n```xml\n<!-- XML: layout_header.xml -->\n<merge>\n    <ImageView android:id=\"@+id/avatar\" />\n    <TextView android:id=\"@+id/name\" />\n</merge>\n\n<!-- Usage -->\n<include layout=\"@layout/layout_header\" />\n```\n\n```kotlin\n// Compose: Extract as a reusable Composable\n@Composable\nfun HeaderSection(\n    avatarUrl: String,\n    name: String,\n    modifier: Modifier = Modifier\n) {\n    Row(modifier = modifier) {\n        AsyncImage(model = avatarUrl, contentDescription = null)\n        Text(text = name)\n    }\n}\n\n// Usage\nHeaderSection(avatarUrl = user.avatar, name = user.name)\n```\n\n---\n\n## Incremental Migration (Interop)\n\n### Embedding Compose in XML\n\n```xml\n<!-- In your XML layout -->\n<androidx.compose.ui.platform.ComposeView\n    android:id=\"@+id/compose_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\" />\n```\n\n```kotlin\n// In Fragment/Activity\nbinding.composeView.setContent {\n    MaterialTheme {\n        MyComposable()\n    }\n}\n```\n\n### Embedding XML Views in Compose\n\n```kotlin\n// Use AndroidView for Views that don't have Compose equivalents\n@Composable\nfun MapViewComposable(modifier: Modifier = Modifier) {\n    AndroidView(\n        factory = { context ->\n            MapView(context).apply {\n                // Initialize the view\n            }\n        },\n        update = { mapView ->\n            // Update the view when state changes\n        },\n        modifier = modifier\n    )\n}\n```\n\n---\n\n## State Migration\n\n### LiveData to Compose\n\n```kotlin\n// Before: Observing in Fragment\nviewModel.uiState.observe(viewLifecycleOwner) { state ->\n    binding.title.text = state.title\n}\n\n// After: Collecting in Compose\n@Composable\nfun MyScreen(viewModel: MyViewModel = hiltViewModel()) {\n    val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n    \n    Text(text = uiState.title)\n}\n```\n\n### Click Listeners\n\n```kotlin\n// Before: XML + setOnClickListener\nbinding.submitButton.setOnClickListener {\n    viewModel.submit()\n}\n\n// After: Lambda in Compose\nButton(onClick = { viewModel.submit() }) {\n    Text(\"Submit\")\n}\n```\n\n---\n\n## Checklist\n\n- [ ] All layouts converted (no `include` or `merge` left)\n- [ ] State hoisted properly (no internal mutable state for user input)\n- [ ] Click handlers converted to lambdas\n- [ ] RecyclerView adapters removed (using LazyColumn/LazyRow)\n- [ ] ViewBinding/DataBinding removed\n- [ ] Navigation integrated (NavHost or interop)\n- [ ] Theming applied (MaterialTheme)\n- [ ] Accessibility preserved (content descriptions, touch targets)\n- [ ] Preview annotations added for development\n- [ ] Old XML files deleted\n\n## References\n\n- [Interoperability APIs](https://developer.android.com/develop/ui/compose/migrate/interoperability-apis)\n- [Migration Strategy](https://developer.android.com/develop/ui/compose/migrate/strategy)\n- [Compose and Views side by side](https://developer.android.com/develop/ui/compose/migrate)\n","frontmatter":{"name":"xml-to-compose-migration","description":"Convert Android XML layouts to Jetpack Compose. Use when asked to migrate Views to Compose, convert XML to Composables, or modernize UI from View system to Compose."},"isInternal":false,"tokens":2445,"sizeBytes":10215},{"name":"app_launcher.py","path":".claude/skills/android-emulator-skill/scripts/app_launcher.py","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/app_launcher.py","title":"Claude Directive: app_launcher.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: app_launcher.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/app_launcher.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/app_launcher.py)","tokens":300,"sizeBytes":260},{"name":"build_and_test.py","path":".claude/skills/android-emulator-skill/scripts/build_and_test.py","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/build_and_test.py","title":"Claude Directive: build_and_test.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: build_and_test.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/build_and_test.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/build_and_test.py)","tokens":300,"sizeBytes":266},{"name":"common.py","path":".claude/skills/android-emulator-skill/scripts/common.py","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/common.py","title":"Claude Directive: common.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: common.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/common.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/common.py)","tokens":300,"sizeBytes":242},{"name":"emu_health_check.sh","path":".claude/skills/android-emulator-skill/scripts/emu_health_check.sh","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/emu_health_check.sh","title":"Claude Directive: emu_health_check.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: emu_health_check.sh\n\nPath: `.claude/skills/android-emulator-skill/scripts/emu_health_check.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/emu_health_check.sh)","tokens":300,"sizeBytes":272},{"name":"emulator_manage.py","path":".claude/skills/android-emulator-skill/scripts/emulator_manage.py","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/emulator_manage.py","title":"Claude Directive: emulator_manage.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: emulator_manage.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/emulator_manage.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/emulator_manage.py)","tokens":300,"sizeBytes":269},{"name":"gesture.py","path":".claude/skills/android-emulator-skill/scripts/gesture.py","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/gesture.py","title":"Claude Directive: gesture.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: gesture.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/gesture.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/gesture.py)","tokens":300,"sizeBytes":245},{"name":"keyboard.py","path":".claude/skills/android-emulator-skill/scripts/keyboard.py","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/keyboard.py","title":"Claude Directive: keyboard.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: keyboard.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/keyboard.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/keyboard.py)","tokens":300,"sizeBytes":248},{"name":"log_monitor.py","path":".claude/skills/android-emulator-skill/scripts/log_monitor.py","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/log_monitor.py","title":"Claude Directive: log_monitor.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: log_monitor.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/log_monitor.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/log_monitor.py)","tokens":300,"sizeBytes":257},{"name":"navigator.py","path":".claude/skills/android-emulator-skill/scripts/navigator.py","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/navigator.py","title":"Claude Directive: navigator.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: navigator.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/navigator.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/navigator.py)","tokens":300,"sizeBytes":251},{"name":"screen_mapper.py","path":".claude/skills/android-emulator-skill/scripts/screen_mapper.py","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/screen_mapper.py","title":"Claude Directive: screen_mapper.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: screen_mapper.py\n\nPath: `.claude/skills/android-emulator-skill/scripts/screen_mapper.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/scripts/screen_mapper.py)","tokens":300,"sizeBytes":263},{"name":"suggest-kmp-dependency.sh","path":".claude/skills/kmp/scripts/suggest-kmp-dependency.sh","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/scripts/suggest-kmp-dependency.sh","title":"Claude Directive: suggest-kmp-dependency.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: suggest-kmp-dependency.sh\n\nPath: `.claude/skills/kmp/scripts/suggest-kmp-dependency.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/scripts/suggest-kmp-dependency.sh)","tokens":300,"sizeBytes":252},{"name":"validate-kmp-structure.sh","path":".claude/skills/kmp/scripts/validate-kmp-structure.sh","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/scripts/validate-kmp-structure.sh","title":"Claude Directive: validate-kmp-structure.sh","category":"claude-rule","format":"markdown","content":"# Claude Directive: validate-kmp-structure.sh\n\nPath: `.claude/skills/kmp/scripts/validate-kmp-structure.sh`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/scripts/validate-kmp-structure.sh)","tokens":300,"sizeBytes":252},{"name":"charts.csv","path":".claude/skills/ui-ux-pro-max/data/charts.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/charts.csv","title":"Claude Directive: charts.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: charts.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/charts.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/charts.csv)","tokens":300,"sizeBytes":221},{"name":"colors.csv","path":".claude/skills/ui-ux-pro-max/data/colors.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/colors.csv","title":"Claude Directive: colors.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: colors.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/colors.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/colors.csv)","tokens":300,"sizeBytes":221},{"name":"icons.csv","path":".claude/skills/ui-ux-pro-max/data/icons.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/icons.csv","title":"Claude Directive: icons.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: icons.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/icons.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/icons.csv)","tokens":300,"sizeBytes":218},{"name":"landing.csv","path":".claude/skills/ui-ux-pro-max/data/landing.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/landing.csv","title":"Claude Directive: landing.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: landing.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/landing.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/landing.csv)","tokens":300,"sizeBytes":224},{"name":"products.csv","path":".claude/skills/ui-ux-pro-max/data/products.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/products.csv","title":"Claude Directive: products.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: products.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/products.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/products.csv)","tokens":300,"sizeBytes":227},{"name":"prompts.csv","path":".claude/skills/ui-ux-pro-max/data/prompts.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/prompts.csv","title":"Claude Directive: prompts.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: prompts.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/prompts.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/prompts.csv)","tokens":300,"sizeBytes":224},{"name":"flutter.csv","path":".claude/skills/ui-ux-pro-max/data/stacks/flutter.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv","title":"Claude Directive: flutter.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: flutter.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv)","tokens":300,"sizeBytes":238},{"name":"html-tailwind.csv","path":".claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv","title":"Claude Directive: html-tailwind.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: html-tailwind.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv)","tokens":300,"sizeBytes":256},{"name":"nextjs.csv","path":".claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv","title":"Claude Directive: nextjs.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: nextjs.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv)","tokens":300,"sizeBytes":235},{"name":"nuxt-ui.csv","path":".claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv","title":"Claude Directive: nuxt-ui.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: nuxt-ui.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv)","tokens":300,"sizeBytes":238},{"name":"nuxtjs.csv","path":".claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv","title":"Claude Directive: nuxtjs.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: nuxtjs.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv)","tokens":300,"sizeBytes":235},{"name":"react-native.csv","path":".claude/skills/ui-ux-pro-max/data/stacks/react-native.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv","title":"Claude Directive: react-native.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: react-native.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv)","tokens":300,"sizeBytes":253},{"name":"react.csv","path":".claude/skills/ui-ux-pro-max/data/stacks/react.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/react.csv","title":"Claude Directive: react.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: react.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/react.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/react.csv)","tokens":300,"sizeBytes":232},{"name":"shadcn.csv","path":".claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv","title":"Claude Directive: shadcn.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: shadcn.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv)","tokens":300,"sizeBytes":235},{"name":"svelte.csv","path":".claude/skills/ui-ux-pro-max/data/stacks/svelte.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv","title":"Claude Directive: svelte.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: svelte.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv)","tokens":300,"sizeBytes":235},{"name":"swiftui.csv","path":".claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv","title":"Claude Directive: swiftui.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: swiftui.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv)","tokens":300,"sizeBytes":238},{"name":"vue.csv","path":".claude/skills/ui-ux-pro-max/data/stacks/vue.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv","title":"Claude Directive: vue.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: vue.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/stacks/vue.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv)","tokens":300,"sizeBytes":226},{"name":"styles.csv","path":".claude/skills/ui-ux-pro-max/data/styles.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/styles.csv","title":"Claude Directive: styles.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: styles.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/styles.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/styles.csv)","tokens":300,"sizeBytes":221},{"name":"typography.csv","path":".claude/skills/ui-ux-pro-max/data/typography.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/typography.csv","title":"Claude Directive: typography.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: typography.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/typography.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/typography.csv)","tokens":300,"sizeBytes":233},{"name":"ux-guidelines.csv","path":".claude/skills/ui-ux-pro-max/data/ux-guidelines.csv","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv","title":"Claude Directive: ux-guidelines.csv","category":"claude-rule","format":"markdown","content":"# Claude Directive: ux-guidelines.csv\n\nPath: `.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv)","tokens":300,"sizeBytes":242},{"name":"core.py","path":".claude/skills/ui-ux-pro-max/scripts/core.py","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/scripts/core.py","title":"Claude Directive: core.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: core.py\n\nPath: `.claude/skills/ui-ux-pro-max/scripts/core.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/scripts/core.py)","tokens":300,"sizeBytes":218},{"name":"search.py","path":".claude/skills/ui-ux-pro-max/scripts/search.py","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/scripts/search.py","title":"Claude Directive: search.py","category":"claude-rule","format":"markdown","content":"# Claude Directive: search.py\n\nPath: `.claude/skills/ui-ux-pro-max/scripts/search.py`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/ui-ux-pro-max/scripts/search.py)","tokens":300,"sizeBytes":224},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/CLAUDE.md)","tokens":300,"sizeBytes":162},{"name":"mobile-developer.md","path":".claude/agents/mobile-developer.md","rawUrl":"https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/agents/mobile-developer.md","title":"Subagent: mobile-developer","category":"subagent-persona","format":"markdown","content":"# Subagent: mobile-developer\n\nPath: `.claude/agents/mobile-developer.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/agents/mobile-developer.md)","tokens":300,"sizeBytes":199}],"systemPromptSnippet":"<yakaai_skills repo=\"maxrave-dev/SimpMusic\">\n<!-- File: .claude/skills/android-accessibility/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# android-accessibility\n\nPath: `.claude/skills/android-accessibility/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-accessibility/SKILL.md)\n\n<!-- File: .claude/skills/android-architecture/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# android-architecture\n\nPath: `.claude/skills/android-architecture/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-architecture/SKILL.md)\n\n<!-- File: .claude/skills/android-coroutines/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# android-coroutines\n\nPath: `.claude/skills/android-coroutines/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-coroutines/SKILL.md)\n\n<!-- File: .claude/skills/android-data-layer/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# android-data-layer\n\nPath: `.claude/skills/android-data-layer/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-data-layer/SKILL.md)\n\n<!-- File: .claude/skills/android-emulator-skill/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# android-emulator-skill\n\nPath: `.claude/skills/android-emulator-skill/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-emulator-skill/SKILL.md)\n\n<!-- File: .claude/skills/android-gradle-logic/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# android-gradle-logic\n\nPath: `.claude/skills/android-gradle-logic/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-gradle-logic/SKILL.md)\n\n<!-- File: .claude/skills/android-retrofit/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# android-retrofit\n\nPath: `.claude/skills/android-retrofit/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-retrofit/SKILL.md)\n\n<!-- File: .claude/skills/android-testing/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# android-testing\n\nPath: `.claude/skills/android-testing/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-testing/SKILL.md)\n\n<!-- File: .claude/skills/android-viewmodel/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# android-viewmodel\n\nPath: `.claude/skills/android-viewmodel/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/android-viewmodel/SKILL.md)\n\n<!-- File: .claude/skills/coil-compose/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# coil-compose\n\nPath: `.claude/skills/coil-compose/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/coil-compose/SKILL.md)\n\n<!-- File: .claude/skills/compose-navigation/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# compose-navigation\n\nPath: `.claude/skills/compose-navigation/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/compose-navigation/SKILL.md)\n\n<!-- File: .claude/skills/compose-performance-audit/SKILL.md (Tokens: ~1580 | Category: anthropic-skill) -->\n---\nname: compose-performance-audit\ndescription: Audit and improve Jetpack Compose runtime performance from code review and architecture. Use when asked to diagnose slow rendering, janky scrolling, excessive recompositions, or performance issues in Compose UI.\n---\n\n# Compose Performance Audit\n\n## Overview\n\nAudit Jetpack Compose view performance end-to-end, from instrumentation and baselining to root-cause analysis and concrete remediation steps.\n\n## Workflow Decision Tree\n\n- If the user provides code, start with \"Code-First Review.\"\n- If the user only describes symptoms, ask for minimal code/context, then do \"Code-First Review.\"\n- If code review is inconclusive, go to \"Guide the User to Profile\" and ask for Layout Inspector output or Perfetto traces.\n\n## 1. Code-First Review\n\nCollect:\n- Target Composable code.\n- Data flow: state, remember, derived state, ViewModel connections.\n- Symptoms and reproduction steps.\n\nFocus on:\n- **Recomposition storms** from unstable parameters or broad state changes.\n- **Unstable keys** in `LazyColumn`/`LazyRow` (`key` churn, missing keys).\n- **Heavy work in composition** (formatting, sorting, filtering, object allocation).\n- **Unnecessary recompositions** (missing `remember`, unstable classes, lambdas).\n- **Large images** without proper sizing or async loading.\n- **Layout thrash** (deep nesting, intrinsic measurements, `SubcomposeLayout` misuse).\n\nProvide:\n- Likely root causes with code references.\n- Suggested fixes and refactors.\n- If needed, a minimal repro or instrumentation suggestion.\n\n## 2. Guide the User to Profile\n\nExplain how to collect data:\n- Use **Layout Inspector** in Android Studio to see recomposition counts.\n- Enable **Recomposition Highlights** in Compose tooling.\n- Use **Perfetto** or **System Trace** for frame timing analysis.\n- Check **Macrobenchmark** results for startup/scroll metrics.\n\nAsk for:\n- Layout Inspector screenshot showing recomposition counts.\n- Perfetto trace or System Trace export.\n- Device/OS/build configuration (debug vs release).\n\n> **Important**: Ensure profiling is done on a **release build** with R8 enabled. Debug builds have significant overhead.\n\n## 3. Analyze and Diagnose\n\nPrioritize likely Compose culprits:\n- **Recomposition storms** from unstable parameters or broad state changes.\n- **Unstable keys** in lazy lists (`key` churn, index-based keys).\n- **Heavy work in composition** (formatting, sorting, object allocation).\n- **Missing `remember`** causing recreations on every recomposition.\n- **Large images** without `Modifier.size()` constraints.\n- **Unnecessary state reads** in wrong composition phases.\n\nSummarize findings with evidence from traces/Layout Inspector.\n\n## 4. Remediate\n\nApply targeted fixes:\n- **Stabilize parameters**: Use `@Stable` or `@Immutable` annotations on data classes.\n- **Stabilize keys**: Use stable, unique IDs for `LazyColumn`/`LazyRow` items.\n- **Defer state reads**: Use `derivedStateOf`, lambda-based modifiers, or `Modifier.drawBehind`.\n- **Remember expensive computations**: Wrap in `remember { }` or `remember(key) { }`.\n- **Skip recomposition**: Extract stable composables, use `key()` to control identity.\n- **Async image loading**: Use Coil/Glide with proper sizing constraints.\n- **Reduce layout complexity**: Flatten hierarchies, avoid deep nesting.\n\n## Common Code Smells (and Fixes)\n\n### Unstable lambda captures\n\n```kotlin\n// BAD: New lambda instance every recomposition\nButton(onClick = { viewModel.doSomething(item) }) { ... }\n\n// GOOD: Use remember or method reference\nval onClick = remember(item) { { viewModel.doSomething(item) } }\nButton(onClick = onClick) { ... }\n```\n\n### Expensive work in composition\n\n```kotlin\n// BAD: Sorting on every recomposition\n@Composable\nfun ItemList(items: List<Item>) {\n    val sorted = items.sortedBy { it.name } // Runs every recomposition\n    LazyColumn { items(sorted) { ... } }\n}\n\n// GOOD: Use remember with key\n@Composable\nfun ItemList(items: List<Item>) {\n    val sorted = remember(items) { items.sortedBy { it.name } }\n    LazyColumn { items(sorted) { ... } }\n}\n```\n\n### Missing keys in LazyColumn\n\n```kotlin\n// BAD: Index-based identity (causes recomposition on list changes)\nLazyColumn {\n    items(items) { item -> ItemRow(item) }\n}\n\n// GOOD: Stable key-based identity\nLazyColumn {\n    items(items, key = { it.id }) { item -> ItemRow(item) }\n}\n```\n\n### Unstable data classes\n\n```kotlin\n// BAD: Unstable (contains List, which is not stable)\ndata class UiState(\n    val items: List<Item>,\n    val isLoading: Boolean\n)\n\n// GOOD: Mark as Immutable if truly immutable\n@Immutable\ndata class UiState(\n    val items: ImmutableList<Item>, // kotlinx.collections.immutable\n    val isLoading: Boolean\n)\n```\n\n### Reading state too early\n\n```kotlin\n// BAD: State read during composition (recomposes whole tree)\n@Composable\nfun AnimatedBox(scrollState: ScrollState) {\n    val offset = scrollState.value // Recomposes on every scroll\n    Box(modifier = Modifier.offset(y = offset.dp)) { ... }\n}\n\n// GOOD: Defer state read to layout/draw phase\n@Composable\nfun AnimatedBox(scrollState: ScrollState) {\n    Box(modifier = Modifier.offset {\n        IntOffset(0, scrollState.value) // Read in layout phase\n    }) { ... }\n}\n```\n\n### Object allocation in composition\n\n```kotlin\n// BAD: Creates new Modifier chain every recomposition\nBox(modifier = Modifier.padding(16.dp).background(Color.Red))\n\n// GOOD for dynamic modifiers: Remember the modifier\nval modifier = remember { Modifier.padding(16.dp).background(Color.Red) }\nBox(modifier = modifier)\n```\n\n## Stability Checklist\n\n| Type | Stable by Default? | Fix |\n|------|-------------------|-----|\n| Primitives (`Int`, `String`, `Boolean`) | Yes | N/A |\n| `data class` with stable fields | Yes* | Ensure all fields are stable |\n| `List`, `Map`, `Set` | **No** | Use `ImmutableList` from kotlinx |\n| Classes with `var` properties | **No** | Use `@Stable` if externally stable |\n| Lambdas | **No** | Use `remember { }` |\n\n## 5. Verify\n\nAsk the user to:\n- Re-run Layout Inspector and compare recomposition counts.\n- Run Macrobenchmark and compare frame timing.\n- Test on a real device with release build.\n\nSummarize the delta (recomposition count, frame drops, jank) if provided.\n\n## Outputs\n\nProvide:\n- A short metrics table (before/after if available).\n- Top issues (ordered by impact).\n- Proposed fixes with estimated effort.\n\n## References\n\n- [Jetpack Compose Performance](https://developer.android.com/develop/ui/compose/performance)\n- [Compose Stability Explained](https://developer.android.com/develop/ui/compose/performance/stability)\n- [Debugging Recomposition](https://developer.android.com/develop/ui/compose/tooling/layout-inspector)\n- [Macrobenchmark](https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview)\n\n<!-- File: .claude/skills/compose-ui/SKILL.md (Tokens: ~602 | Category: anthropic-skill) -->\n---\nname: compose-ui\ndescription: Best practices for building UI with Jetpack Compose, focusing on state hoisting, detailed performance optimizations, and theming. Use this when writing or refactoring Composable functions.\n---\n\n# Jetpack Compose Best Practices\n\n## Instructions\n\nFollow these guidelines to create performant, reusable, and testable Composables.\n\n### 1. State Hoisting (Unidirectional Data Flow)\nMake Composables **stateless** whenever possible by moving state to the caller.\n\n*   **Pattern**: Function signature should usually look like:\n    ```kotlin\n    @Composable\n    fun MyComponent(\n        value: String,              // State flows down\n        onValueChange: (String) -> Unit, // Events flow up\n        modifier: Modifier = Modifier // Standard modifier parameter\n    )\n    ```\n*   **Benefit**: Decouples the UI from simple state storage, making it easier to preview and test.\n*   **ViewModel Integration**: The screen-level Composable retrieves state from the ViewModel (`viewModel.uiState.collectAsStateWithLifecycle()`) and passes it down.\n\n### 2. Modifiers\n*   **Default Parameter**: Always provide a `modifier: Modifier = Modifier` as the first optional parameter.\n*   **Application**: Apply this `modifier` to the *root* layout element of your Composable.\n*   **Ordering matters**: `padding().clickable()` is different from `clickable().padding()`. Generally apply layout-affecting modifiers (like padding) *after* click listeners if you want the padding to be clickable.\n\n### 3. Performance Optimization\n*   **`remember`**: Use `remember { ... }` to cache expensive calculations across recompositions.\n*   **`derivedStateOf`**: Use `derivedStateOf { ... }` when a state changes frequently (like scroll position) but the UI only needs to react to a threshold or summary (e.g., show \"Jump to Top\" button). This prevents unnecessary recompositions.\n    ```kotlin\n    val showButton by remember {\n        derivedStateOf { listState.firstVisibleItemIndex > 0 }\n    }\n    ```\n*   **Lambda Stability**: Prefer method references (e.g., `viewModel::onEvent`) or remembered lambdas to prevent unstable types from triggering recomposition of children.\n\n### 4. Theming and Resources\n*   Use `MaterialTheme.colorScheme` and `MaterialTheme.typography` instead of hardcoded colors or text styles.\n*   Organize simple UI components into specific files (e.g., `DesignSystem.kt` or `Components.kt`) if they are shared across features.\n\n### 5. Previews\n*   Create a private preview function for every public Composable.\n*   Use `@Preview(showBackground = true)` and include Light/Dark mode previews if applicable.\n*   Pass dummy data (static) to the stateless Composable for the preview.\n\n<!-- File: .claude/skills/find-skills/SKILL.md (Tokens: ~1110 | Category: anthropic-skill) -->\n---\nname: find-skills\ndescription: Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.\n---\n\n# Find Skills\n\nThis skill helps you discover and install skills from the open agent skills ecosystem.\n\n## When to Use This Skill\n\nUse this skill when the user:\n\n- Asks \"how do I do X\" where X might be a common task with an existing skill\n- Says \"find a skill for X\" or \"is there a skill for X\"\n- Asks \"can you do X\" where X is a specialized capability\n- Expresses interest in extending agent capabilities\n- Wants to search for tools, templates, or workflows\n- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)\n\n## What is the Skills CLI?\n\nThe Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.\n\n**Key commands:**\n\n- `npx skills find [query]` - Search for skills interactively or by keyword\n- `npx skills add <package>` - Install a skill from GitHub or other sources\n- `npx skills check` - Check for skill updates\n- `npx skills update` - Update all installed skills\n\n**Browse skills at:** https://skills.sh/\n\n## How to Help Users Find Skills\n\n### Step 1: Understand What They Need\n\nWhen a user asks for help with something, identify:\n\n1. The domain (e.g., React, testing, design, deployment)\n2. The specific task (e.g., writing tests, creating animations, reviewing PRs)\n3. Whether this is a common enough task that a skill likely exists\n\n### Step 2: Search for Skills\n\nRun the find command with a relevant query:\n\n```bash\nnpx skills find [query]\n```\n\nFor example:\n\n- User asks \"how do I make my React app faster?\" → `npx skills find react performance`\n- User asks \"can you help me with PR reviews?\" → `npx skills find pr review`\n- User asks \"I need to create a changelog\" → `npx skills find changelog`\n\nThe command will return results like:\n\n```\nInstall with npx skills add <owner/repo@skill>\n\nvercel-labs/agent-skills@vercel-react-best-practices\n└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 3: Present Options to the User\n\nWhen you find relevant skills, present them to the user with:\n\n1. The skill name and what it does\n2. The install command they can run\n3. A link to learn more at skills.sh\n\nExample response:\n\n```\nI found a skill that might help! The \"vercel-react-best-practices\" skill provides\nReact and Next.js performance optimization guidelines from Vercel Engineering.\n\nTo install it:\nnpx skills add vercel-labs/agent-skills@vercel-react-best-practices\n\nLearn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices\n```\n\n### Step 4: Offer to Install\n\nIf the user wants to proceed, you can install the skill for them:\n\n```bash\nnpx skills add <owner/repo@skill> -g -y\n```\n\nThe `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.\n\n## Common Skill Categories\n\nWhen searching, consider these common categories:\n\n| Category        | Example Queries                          |\n| --------------- | ---------------------------------------- |\n| Web Development | react, nextjs, typescript, css, tailwind |\n| Testing         | testing, jest, playwright, e2e           |\n| DevOps          | deploy, docker, kubernetes, ci-cd        |\n| Documentation   | docs, readme, changelog, api-docs        |\n| Code Quality    | review, lint, refactor, best-practices   |\n| Design          | ui, ux, design-system, accessibility     |\n| Productivity    | workflow, automation, git                |\n\n## Tips for Effective Searches\n\n1. **Use specific keywords**: \"react testing\" is better than just \"testing\"\n2. **Try alternative terms**: If \"deploy\" doesn't work, try \"deployment\" or \"ci-cd\"\n3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`\n\n## When No Skills Are Found\n\nIf no relevant skills exist:\n\n1. Acknowledge that no existing skill was found\n2. Offer to help with the task directly using your general capabilities\n3. Suggest the user could create their own skill with `npx skills init`\n\nExample:\n\n```\nI searched for skills related to \"xyz\" but didn't find any matches.\nI can still help you with this task directly! Would you like me to proceed?\n\nIf this is something you do often, you could create your own skill:\nnpx skills init my-xyz-skill\n```\n\n<!-- File: .claude/skills/gradle-build-performance/SKILL.md (Tokens: ~1956 | Category: anthropic-skill) -->\n---\nname: gradle-build-performance\ndescription: Debug and optimize Android/Gradle build performance. Use when builds are slow, investigating CI/CD performance, analyzing build scans, or identifying compilation bottlenecks.\n---\n\n# Gradle Build Performance\n\n## When to Use\n\n- Build times are slow (clean or incremental)\n- Investigating build performance regressions\n- Analyzing Gradle Build Scans\n- Identifying configuration vs execution bottlenecks\n- Optimizing CI/CD build times\n- Enabling Gradle Configuration Cache\n- Reducing unnecessary recompilation\n- Debugging kapt/KSP annotation processing\n\n## Example Prompts\n\n- \"My builds are slow, how can I speed them up?\"\n- \"How do I analyze a Gradle build scan?\"\n- \"Why is configuration taking so long?\"\n- \"Why does my project always recompile everything?\"\n- \"How do I enable configuration cache?\"\n- \"Why is kapt so slow?\"\n\n---\n\n## Workflow\n\n1. **Measure Baseline** — Clean build + incremental build times\n2. **Generate Build Scan** — `./gradlew assembleDebug --scan`\n3. **Identify Phase** — Configuration? Execution? Dependency resolution?\n4. **Apply ONE optimization** — Don't batch changes\n5. **Measure Improvement** — Compare against baseline\n6. **Verify in Build Scan** — Visual confirmation\n\n---\n\n## Quick Diagnostics\n\n### Generate Build Scan\n\n```bash\n./gradlew assembleDebug --scan\n```\n\n### Profile Build Locally\n\n```bash\n./gradlew assembleDebug --profile\n# Opens report in build/reports/profile/\n```\n\n### Build Timing Summary\n\n```bash\n./gradlew assembleDebug --info | grep -E \"^\\:.*\"\n# Or view in Android Studio: Build > Analyze APK Build\n```\n\n---\n\n## Build Phases\n\n| Phase | What Happens | Common Issues |\n|-------|--------------|---------------|\n| **Initialization** | `settings.gradle.kts` evaluated | Too many `include()` statements |\n| **Configuration** | All `build.gradle.kts` files evaluated | Expensive plugins, eager task creation |\n| **Execution** | Tasks run based on inputs/outputs | Cache misses, non-incremental tasks |\n\n### Identify the Bottleneck\n\n```\nBuild scan → Performance → Build timeline\n```\n\n- **Long configuration phase**: Focus on plugin and buildscript optimization\n- **Long execution phase**: Focus on task caching and parallelization\n- **Dependency resolution slow**: Focus on repository configuration\n\n---\n\n## 12 Optimization Patterns\n\n### 1. Enable Configuration Cache\n\nCaches configuration phase across builds (AGP 8.0+):\n\n```properties\n# gradle.properties\norg.gradle.configuration-cache=true\norg.gradle.configuration-cache.problems=warn\n```\n\n### 2. Enable Build Cache\n\nReuses task outputs across builds and machines:\n\n```properties\n# gradle.properties\norg.gradle.caching=true\n```\n\n### 3. Enable Parallel Execution\n\nBuild independent modules simultaneously:\n\n```properties\n# gradle.properties\norg.gradle.parallel=true\n```\n\n### 4. Increase JVM Heap\n\nAllocate more memory for large projects:\n\n```properties\n# gradle.properties\norg.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC\n```\n\n### 5. Use Non-Transitive R Classes\n\nReduces R class size and compilation (AGP 8.0+ default):\n\n```properties\n# gradle.properties\nandroid.nonTransitiveRClass=true\n```\n\n### 6. Migrate kapt to KSP\n\nKSP is 2x faster than kapt for Kotlin:\n\n```kotlin\n// Before (slow)\nkapt(\"com.google.dagger:hilt-compiler:2.51.1\")\n\n// After (fast)\nksp(\"com.google.dagger:hilt-compiler:2.51.1\")\n```\n\n### 7. Avoid Dynamic Dependencies\n\nPin dependency versions:\n\n```kotlin\n// BAD: Forces resolution every build\nimplementation(\"com.example:lib:+\")\nimplementation(\"com.example:lib:1.0.+\")\n\n// GOOD: Fixed version\nimplementation(\"com.example:lib:1.2.3\")\n```\n\n### 8. Optimize Repository Order\n\nPut most-used repositories first:\n\n```kotlin\n// settings.gradle.kts\ndependencyResolutionManagement {\n    repositories {\n        google()      // First: Android dependencies\n        mavenCentral() // Second: Most libraries\n        // Third-party repos last\n    }\n}\n```\n\n### 9. Use includeBuild for Local Modules\n\nComposite builds are faster than `project()` for large monorepos:\n\n```kotlin\n// settings.gradle.kts\nincludeBuild(\"shared-library\") {\n    dependencySubstitution {\n        substitute(module(\"com.example:shared\")).using(project(\":\"))\n    }\n}\n```\n\n### 10. Enable Incremental Annotation Processing\n\n```properties\n# gradle.properties\nkapt.incremental.apt=true\nkapt.use.worker.api=true\n```\n\n### 11. Avoid Configuration-Time I/O\n\nDon't read files or make network calls during configuration:\n\n```kotlin\n// BAD: Runs during configuration\nval version = file(\"version.txt\").readText()\n\n// GOOD: Defer to execution\nval version = providers.fileContents(file(\"version.txt\")).asText\n```\n\n### 12. Use Lazy Task Configuration\n\nAvoid `create()`, use `register()`:\n\n```kotlin\n// BAD: Eagerly configured\ntasks.create(\"myTask\") { ... }\n\n// GOOD: Lazily configured\ntasks.register(\"myTask\") { ... }\n```\n\n---\n\n## Common Bottleneck Analysis\n\n### Slow Configuration Phase\n\n**Symptoms**: Build scan shows long \"Configuring build\" time\n\n**Causes & Fixes**:\n| Cause | Fix |\n|-------|-----|\n| Eager task creation | Use `tasks.register()` instead of `tasks.create()` |\n| buildSrc with many dependencies | Migrate to Convention Plugins with `includeBuild` |\n| File I/O in build scripts | Use `providers.fileContents()` |\n| Network calls in plugins | Cache results or use offline mode |\n\n### Slow Compilation\n\n**Symptoms**: `:app:compileDebugKotlin` takes too long\n\n**Causes & Fixes**:\n| Cause | Fix |\n|-------|-----|\n| Non-incremental changes | Avoid `build.gradle.kts` changes that invalidate cache |\n| Large modules | Break into smaller feature modules |\n| Excessive kapt usage | Migrate to KSP |\n| Kotlin compiler memory | Increase `kotlin.daemon.jvmargs` |\n\n### Cache Misses\n\n**Symptoms**: Tasks always rerun despite no changes\n\n**Causes & Fixes**:\n| Cause | Fix |\n|-------|-----|\n| Unstable task inputs | Use `@PathSensitive`, `@NormalizeLineEndings` |\n| Absolute paths in outputs | Use relative paths |\n| Missing `@CacheableTask` | Add annotation to custom tasks |\n| Different JDK versions | Standardize JDK across environments |\n\n---\n\n## CI/CD Optimizations\n\n### Remote Build Cache\n\n```kotlin\n// settings.gradle.kts\nbuildCache {\n    local { isEnabled = true }\n    remote<HttpBuildCache> {\n        url = uri(\"https://cache.example.com/\")\n        isPush = System.getenv(\"CI\") == \"true\"\n        credentials {\n            username = System.getenv(\"CACHE_USER\")\n            password = System.getenv(\"CACHE_PASS\")\n        }\n    }\n}\n```\n\n### Gradle Enterprise / Develocity\n\nFor advanced build analytics:\n\n```kotlin\n// settings.gradle.kts\nplugins {\n    id(\"com.gradle.develocity\") version \"3.17\"\n}\n\ndevelocity {\n    buildScan {\n        termsOfUseUrl.set(\"https://gradle.com/help/legal-terms-of-use\")\n        termsOfUseAgree.set(\"yes\")\n        publishing.onlyIf { System.getenv(\"CI\") != null }\n    }\n}\n```\n\n### Skip Unnecessary Tasks in CI\n\n```bash\n# Skip tests for UI-only changes\n./gradlew assembleDebug -x test -x lint\n\n# Only run affected module tests\n./gradlew :feature:login:test\n```\n\n---\n\n## Android Studio Settings\n\n### File → Settings → Build → Gradle\n\n- **Gradle JDK**: Match your project's JDK\n- **Build and run using**: Gradle (not IntelliJ)\n- **Run tests using**: Gradle\n\n### File → Settings → Build → Compiler\n\n- **Compile independent modules in parallel**: ✅ Enabled\n- **Configure on demand**: ❌ Disabled (deprecated)\n\n---\n\n## Verification Checklist\n\nAfter optimizations, verify:\n\n- [ ] Configuration cache enabled and working\n- [ ] Build cache hit rate > 80% (check build scan)\n- [ ] No dynamic dependency versions\n- [ ] KSP used instead of kapt where possible\n- [ ] Parallel execution enabled\n- [ ] JVM memory tuned appropriately\n- [ ] CI remote cache configured\n- [ ] No configuration-time I/O\n\n---\n\n## References\n\n- [Optimize Build Speed](https://developer.android.com/build/optimize-your-build)\n- [Gradle Configuration Cache](https://docs.gradle.org/current/userguide/configuration_cache.html)\n- [Gradle Build Cache](https://docs.gradle.org/current/userguide/build_cache.html)\n- [Migrate from kapt to KSP](https://developer.android.com/build/migrate-to-ksp)\n- [Gradle Build Scans](https://scans.gradle.com/)\n\n<!-- File: .claude/skills/jetpack-compose/SKILL.md (Tokens: ~2030 | Category: anthropic-skill) -->\n---\nname: android-jetpack-compose\ndescription: Use when building Android UIs with Jetpack Compose, managing state with remember/mutableStateOf, or implementing declarative UI patterns.\nallowed-tools:\n  - Read\n  - Write\n  - Edit\n  - Bash\n  - Grep\n  - Glob\n---\n\n# Android - Jetpack Compose\n\nModern declarative UI toolkit for building native Android interfaces.\n\n## Key Concepts\n\n### State Management\n\nCompose provides several ways to manage state:\n\n- **remember**: Survives recomposition\n- **rememberSaveable**: Survives configuration changes\n- **mutableStateOf**: Creates observable state\n- **derivedStateOf**: Computed state that updates when dependencies change\n\n```kotlin\n@Composable\nfun Counter() {\n    var count by remember { mutableStateOf(0) }\n\n    Column {\n        Text(\"Count: $count\")\n        Button(onClick = { count++ }) {\n            Text(\"Increment\")\n        }\n    }\n}\n\n// With saveable for configuration changes\n@Composable\nfun SearchField() {\n    var query by rememberSaveable { mutableStateOf(\"\") }\n\n    TextField(\n        value = query,\n        onValueChange = { query = it },\n        placeholder = { Text(\"Search...\") }\n    )\n}\n```\n\n### State Hoisting\n\nLift state up to make composables stateless and reusable:\n\n```kotlin\n// Stateless composable\n@Composable\nfun NameInput(\n    name: String,\n    onNameChange: (String) -> Unit,\n    modifier: Modifier = Modifier\n) {\n    TextField(\n        value = name,\n        onValueChange = onNameChange,\n        label = { Text(\"Name\") },\n        modifier = modifier\n    )\n}\n\n// Stateful parent\n@Composable\nfun UserForm() {\n    var name by remember { mutableStateOf(\"\") }\n\n    NameInput(\n        name = name,\n        onNameChange = { name = it }\n    )\n}\n```\n\n### ViewModel Integration\n\n```kotlin\nclass UserViewModel : ViewModel() {\n    private val _uiState = MutableStateFlow(UserUiState())\n    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()\n\n    fun updateName(name: String) {\n        _uiState.update { it.copy(name = name) }\n    }\n\n    fun saveUser() {\n        viewModelScope.launch {\n            _uiState.update { it.copy(isLoading = true) }\n            try {\n                userRepository.save(_uiState.value.toUser())\n                _uiState.update { it.copy(isLoading = false, isSaved = true) }\n            } catch (e: Exception) {\n                _uiState.update { it.copy(isLoading = false, error = e.message) }\n            }\n        }\n    }\n}\n\n@Composable\nfun UserScreen(viewModel: UserViewModel = viewModel()) {\n    val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n\n    UserContent(\n        uiState = uiState,\n        onNameChange = viewModel::updateName,\n        onSave = viewModel::saveUser\n    )\n}\n```\n\n## Best Practices\n\n### Composable Function Guidelines\n\n```kotlin\n// Use Modifier as first optional parameter\n@Composable\nfun CustomCard(\n    title: String,\n    modifier: Modifier = Modifier,\n    onClick: () -> Unit = {}\n) {\n    Card(\n        modifier = modifier.clickable(onClick = onClick)\n    ) {\n        Text(\n            text = title,\n            modifier = Modifier.padding(16.dp)\n        )\n    }\n}\n\n// Use slot APIs for flexible content\n@Composable\nfun CustomScaffold(\n    topBar: @Composable () -> Unit = {},\n    bottomBar: @Composable () -> Unit = {},\n    content: @Composable (PaddingValues) -> Unit\n) {\n    Scaffold(\n        topBar = topBar,\n        bottomBar = bottomBar,\n        content = content\n    )\n}\n```\n\n### Efficient Recomposition\n\n```kotlin\n// Use keys for list items\n@Composable\nfun UserList(users: List<User>) {\n    LazyColumn {\n        items(\n            items = users,\n            key = { it.id }  // Stable key for efficient updates\n        ) { user ->\n            UserItem(user)\n        }\n    }\n}\n\n// Use derivedStateOf for expensive computations\n@Composable\nfun FilteredList(items: List<Item>, query: String) {\n    val filteredItems by remember(items, query) {\n        derivedStateOf {\n            items.filter { it.name.contains(query, ignoreCase = true) }\n        }\n    }\n\n    LazyColumn {\n        items(filteredItems) { item ->\n            ItemRow(item)\n        }\n    }\n}\n```\n\n### Side Effects\n\n```kotlin\n// LaunchedEffect for coroutine-based side effects\n@Composable\nfun UserProfile(userId: String, viewModel: UserViewModel) {\n    LaunchedEffect(userId) {\n        viewModel.loadUser(userId)\n    }\n\n    // UI content\n}\n\n// DisposableEffect for cleanup\n@Composable\nfun LifecycleAwareComponent(lifecycle: Lifecycle) {\n    DisposableEffect(lifecycle) {\n        val observer = LifecycleEventObserver { _, event ->\n            // Handle lifecycle events\n        }\n        lifecycle.addObserver(observer)\n\n        onDispose {\n            lifecycle.removeObserver(observer)\n        }\n    }\n}\n\n// SideEffect for non-suspend side effects\n@Composable\nfun AnalyticsScreen(screenName: String) {\n    SideEffect {\n        analytics.logScreenView(screenName)\n    }\n}\n```\n\n## Common Patterns\n\n### Navigation with Navigation Compose\n\n```kotlin\n@Composable\nfun AppNavigation() {\n    val navController = rememberNavController()\n\n    NavHost(navController = navController, startDestination = \"home\") {\n        composable(\"home\") {\n            HomeScreen(\n                onNavigateToDetail = { id ->\n                    navController.navigate(\"detail/$id\")\n                }\n            )\n        }\n        composable(\n            route = \"detail/{itemId}\",\n            arguments = listOf(navArgument(\"itemId\") { type = NavType.StringType })\n        ) { backStackEntry ->\n            val itemId = backStackEntry.arguments?.getString(\"itemId\")\n            DetailScreen(itemId = itemId)\n        }\n    }\n}\n```\n\n### Material 3 Theming\n\n```kotlin\n@Composable\nfun AppTheme(\n    darkTheme: Boolean = isSystemInDarkTheme(),\n    content: @Composable () -> Unit\n) {\n    val colorScheme = when {\n        darkTheme -> darkColorScheme(\n            primary = Purple80,\n            secondary = PurpleGrey80,\n            tertiary = Pink80\n        )\n        else -> lightColorScheme(\n            primary = Purple40,\n            secondary = PurpleGrey40,\n            tertiary = Pink40\n        )\n    }\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        typography = Typography,\n        content = content\n    )\n}\n\n// Using theme values\n@Composable\nfun ThemedCard() {\n    Card(\n        colors = CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surfaceVariant\n        )\n    ) {\n        Text(\n            text = \"Themed content\",\n            style = MaterialTheme.typography.bodyLarge,\n            color = MaterialTheme.colorScheme.onSurfaceVariant\n        )\n    }\n}\n```\n\n### Lists and Grids\n\n```kotlin\n@Composable\nfun ProductGrid(products: List<Product>) {\n    LazyVerticalGrid(\n        columns = GridCells.Adaptive(minSize = 160.dp),\n        contentPadding = PaddingValues(16.dp),\n        horizontalArrangement = Arrangement.spacedBy(16.dp),\n        verticalArrangement = Arrangement.spacedBy(16.dp)\n    ) {\n        items(products, key = { it.id }) { product ->\n            ProductCard(product)\n        }\n    }\n}\n\n// Sticky headers\n@Composable\nfun ContactList(contacts: Map<Char, List<Contact>>) {\n    LazyColumn {\n        contacts.forEach { (initial, contactsForInitial) ->\n            stickyHeader {\n                Text(\n                    text = initial.toString(),\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .background(MaterialTheme.colorScheme.surface)\n                        .padding(16.dp),\n                    style = MaterialTheme.typography.titleMedium\n                )\n            }\n            items(contactsForInitial) { contact ->\n                ContactItem(contact)\n            }\n        }\n    }\n}\n```\n\n## Anti-Patterns\n\n### Avoid Side Effects in Composition\n\nBad:\n\n```kotlin\n@Composable\nfun BadExample(viewModel: ViewModel) {\n    viewModel.loadData()  // Called on every recomposition!\n\n    Text(\"Data loaded\")\n}\n```\n\nGood:\n\n```kotlin\n@Composable\nfun GoodExample(viewModel: ViewModel) {\n    LaunchedEffect(Unit) {\n        viewModel.loadData()\n    }\n\n    Text(\"Data loaded\")\n}\n```\n\n### Don't Read State in Remember Block\n\nBad:\n\n```kotlin\n@Composable\nfun BadCounter(initial: Int) {\n    // Won't update when initial changes\n    var count by remember { mutableStateOf(initial) }\n}\n```\n\nGood:\n\n```kotlin\n@Composable\nfun GoodCounter(initial: Int) {\n    var count by remember(initial) { mutableStateOf(initial) }\n}\n```\n\n### Avoid Heavy Computation During Composition\n\nBad:\n\n```kotlin\n@Composable\nfun BadList(items: List<Item>) {\n    // Runs on every recomposition\n    val sorted = items.sortedBy { it.name }\n    LazyColumn { /* ... */ }\n}\n```\n\nGood:\n\n```kotlin\n@Composable\nfun GoodList(items: List<Item>) {\n    val sorted by remember(items) {\n        derivedStateOf { items.sortedBy { it.name } }\n    }\n    LazyColumn { /* ... */ }\n}\n```\n\n## Related Skills\n\n- **android-architecture**: MVVM and clean architecture patterns\n- **android-kotlin-coroutines**: Async operations in Compose\n\n<!-- File: .claude/skills/kmp/references/abstraction-examples.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/kmp/references/abstraction-examples.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/abstraction-examples.md)\n\n<!-- File: .claude/skills/kmp/references/expect-actual-catalog.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/kmp/references/expect-actual-catalog.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/expect-actual-catalog.md)\n\n<!-- File: .claude/skills/kmp/references/source-set-hierarchy.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/kmp/references/source-set-hierarchy.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/source-set-hierarchy.md)\n\n<!-- File: .claude/skills/kmp/references/target-compatibility.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/kmp/references/target-compatibility.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/kmp/references/target-compatibility.md)\n\n<!-- File: .claude/skills/kmp/SKILL.md (Tokens: ~3318 | Category: anthropic-skill) -->\n---\nname: kotlin-multiplatform\ndescription: |\n  Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific,\n  source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets\n  (Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues.\n  Triggers on: abstraction decisions (\"should I share this?\"), source set placement questions, expect/actual creation,\n  build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.\n---\n\n# Kotlin Multiplatform: Platform Abstraction Decisions\n\nExpert guidance for KMP architecture in Amethyst - deciding what to share vs keep platform-specific.\n\n## When to Use This Skill\n\nMaking platform abstraction decisions:\n- \"Should I create expect/actual or keep Android-only?\"\n- \"Can I share this ViewModel logic?\"\n- \"Where does this crypto/JSON/network implementation belong?\"\n- \"This uses Android Context - can it be abstracted?\"\n- \"Is this code in the wrong module?\"\n- Preparing for iOS/web/wasm targets\n- Detecting incorrect placements\n\n## Abstraction Decision Tree\n\n**Central question:** \"Should this code be reused across platforms?\"\n\nFollow this decision path (< 1 minute):\n\n```\nQ: Is it used by 2+ platforms?\n├─ NO  → Keep platform-specific\n│         Example: Android-only permission handling\n│\n└─ YES → Continue ↓\n\nQ: Is it pure Kotlin (no platform APIs)?\n├─ YES → commonMain\n│         Example: Nostr event parsing, business rules\n│\n└─ NO  → Continue ↓\n\nQ: Does it vary by platform or by JVM vs non-JVM?\n├─ By platform (Android ≠ iOS ≠ Desktop)\n│  → expect/actual\n│  Example: Secp256k1Instance (uses different security APIs)\n│\n├─ By JVM (Android = Desktop ≠ iOS/web)\n│  → jvmAndroid\n│  Example: Jackson JSON parsing (JVM library)\n│\n└─ Complex/UI-related\n   → Keep platform-specific\n   Example: Navigation (Activity vs Window too different)\n\nFinal check:\nQ: Maintenance cost of abstraction < duplication cost?\n├─ YES → Proceed with abstraction\n└─ NO  → Duplicate (simpler)\n```\n\n### Real Examples from Codebase\n\n**Crypto → expect/actual:**\n```kotlin\n// commonMain - expect declaration\nexpect object Secp256k1Instance {\n    fun signSchnorr(data: ByteArray, privKey: ByteArray): ByteArray\n}\n\n// androidMain - uses Android Keystore\n// jvmMain - uses Desktop JVM crypto\n// iosMain - uses iOS Security framework\n```\n**Why:** Each platform has different security APIs.\n\n**JSON parsing → jvmAndroid:**\n```kotlin\n// quartz/build.gradle.kts\nval jvmAndroid = create(\"jvmAndroid\") {\n    api(libs.jackson.module.kotlin)\n}\n```\n**Why:** Jackson is JVM-only, works on Android + Desktop, not iOS/web.\n\n**Navigation → platform-specific:**\n- Android: `MainActivity` (Activity + Compose Navigation)\n- Desktop: `Window` + sidebar + MenuBar\n**Why:** UI paradigms fundamentally different.\n\n## Mental Model: Source Sets as Dependency Graph\n\nThink of source sets as a dependency graph, not folders.\n\n```\n┌─────────────────────────────────────────────┐\n│ commonMain = Contract (pure Kotlin)         │\n│ - Business logic, protocol, data models     │\n│ - No platform APIs                          │\n└────────────┬────────────────────────────────┘\n             │\n             ├──────────────────────┬────────────────────\n             │                      │\n             ▼                      ▼\n   ┌───────────────────┐  ┌──────────────────┐\n   │ jvmAndroid        │  │ iosMain          │\n   │ JVM libs shared   │  │ iOS common       │\n   │ - Jackson         │  │                  │\n   │ - OkHttp          │  └────┬─────────────┘\n   └───┬───────────┬───┘       │\n       │           │            ├─→ iosX64Main\n       ▼           ▼            ├─→ iosArm64Main\n  ┌─────────┐ ┌──────────┐     └─→ iosSimulatorArm64Main\n  │android  │ │jvmMain   │\n  │Main     │ │(Desktop) │\n  └─────────┘ └──────────┘\n\nFuture: jsMain, wasmMain\n```\n\n**Key insight:** jvmAndroid is NOT a platform - it's a shared JVM layer.\n\n## The jvmAndroid Pattern\n\n**Unique to Amethyst.** Shares JVM libraries between Android + Desktop.\n\n### When to Use jvmAndroid\n\nUse jvmAndroid when:\n- ✅ JVM-specific libraries (Jackson, OkHttp, url-detector)\n- ✅ Android implementation = Desktop implementation (same JVM)\n- ✅ Library doesn't work on iOS/web\n\nDo NOT use jvmAndroid for:\n- ❌ Pure Kotlin code (use commonMain)\n- ❌ Platform-specific APIs (use androidMain/jvmMain)\n- ❌ Code that should work on all platforms\n\n### Example from quartz/build.gradle.kts\n\n```kotlin\n// Must be defined BEFORE androidMain and jvmMain\nval jvmAndroid = create(\"jvmAndroid\") {\n    dependsOn(commonMain.get())\n\n    dependencies {\n        api(libs.jackson.module.kotlin)  // JSON parsing - JVM only\n        api(libs.url.detector)            // URL extraction - JVM only\n        implementation(libs.okhttp)       // HTTP client - JVM only\n    }\n}\n\n// Both depend on jvmAndroid\njvmMain { dependsOn(jvmAndroid) }\nandroidMain { dependsOn(jvmAndroid) }\n```\n\n**Why Jackson in jvmAndroid, not commonMain?**\n- Jackson is JVM-specific library\n- Works on Android (runs on JVM)\n- Works on Desktop (runs on JVM)\n- Does NOT work on iOS (not JVM) or web (not JVM)\n\n**Web/wasm consideration:** For future web support, consider migrating from Jackson → kotlinx.serialization (see Target-Specific Guidance).\n\n## What to Abstract vs Keep Platform-Specific\n\nQuick decision guidelines based on codebase patterns:\n\n### Always Abstract\n- **Crypto** (Secp256k1, encryption, signing)\n- **Core protocol logic** (Nostr events, NIPs)\n- **Why:** Needed everywhere, platform security APIs vary\n\n### Often Abstract\n- **I/O operations** (file reading, caching)\n- **Logging** (platform logging systems differ)\n- **Serialization** (if using kotlinx.serialization)\n- **Why:** Commonly reused, platform implementations available\n\n### Sometimes Abstract\n- **Business logic:** YES - state machines, data processing\n- **ViewModels:** YES - state + business logic shareable (StateFlow/SharedFlow)\n- **Screen layouts:** NO - platform-native (Window vs Activity)\n- **Why:** ViewModels contain platform-agnostic state; Screens render differently per platform\n\n### Rarely Abstract\n- **Complex UI components** (composables with heavy platform dependencies)\n- **Why:** Platform paradigms can differ significantly\n\n### Never Abstract\n- **Navigation** (Activity vs Window fundamentally different)\n- **Permissions** (Android vs iOS APIs incompatible)\n- **Platform UX patterns**\n- **Why:** Too platform-specific, abstraction creates leaky APIs\n\n### Evidence from shared-ui-analysis.md\n\n| Component | Shared? | Rationale |\n|-----------|---------|-----------|\n| PubKeyFormatter, ZapFormatter | ✅ YES | Pure Kotlin, no platform APIs |\n| TimeAgoFormatter | ⚠️ ABSTRACTED | Needs StringProvider for localized strings |\n| ViewModels (state + logic) | ✅ YES | StateFlow/SharedFlow platform-agnostic, Compose Multiplatform lifecycle compatible |\n| Screen layouts (Scaffold, nav) | ❌ NO | Window vs Activity, sidebar vs bottom nav fundamentally different |\n| Image loading (Coil) | ⚠️ ABSTRACTED | Coil 3.x supports KMP, needs expect/actual wrapper |\n\n## expect/actual Mechanics\n\n**When to use:** Code needed by 2+ platforms, varies by platform.\n\n### Pattern Categories from Codebase\n\n**Objects (singletons):**\n```kotlin\n// 24 expect declarations found, common pattern:\nexpect object Secp256k1Instance { ... }\nexpect object Log { ... }\nexpect object LibSodiumInstance { ... }\n```\n\n**Classes (instantiable):**\n```kotlin\nexpect class AESCBC { ... }\nexpect class DigestInstance { ... }\n```\n\n**Functions (utilities):**\n```kotlin\nexpect fun platform(): String\nexpect fun currentTimeSeconds(): Long\n```\n\n**See** [references/expect-actual-catalog.md](references/expect-actual-catalog.md) for complete catalog with rationale.\n\n## Target-Specific Guidance\n\n### Android, JVM (Desktop), iOS - Current Primary Targets\n\n**Status:** Mature patterns, stable APIs\n\n**Android (androidMain):**\n- Uses Android framework (Activity, Context, etc.)\n- secp256k1-kmp-jni-android for crypto\n- AndroidX libraries\n\n**Desktop JVM (jvmMain):**\n- Uses Compose Desktop (Window, MenuBar, etc.)\n- secp256k1-kmp-jni-jvm for crypto\n- Pure JVM libraries\n\n**iOS (iosMain):**\n- Active development, framework configured\n- Architecture targets: iosX64Main, iosArm64Main, iosSimulatorArm64Main\n- Platform APIs via platform.posix, Security framework\n\n### Web, wasm - Future Targets\n\n**Status:** Not yet implemented, consider for future-proofing\n\n**Constraints to know:**\n- ❌ No platform.posix (file I/O different)\n- ❌ No JVM libraries (Jackson, OkHttp won't work)\n- ❌ Different async model (JS event loop vs threads)\n\n**Future-proofing tips:**\n1. Prefer pure Kotlin in commonMain\n2. Use kotlinx.* libraries:\n   - kotlinx.serialization instead of Jackson\n   - ktor instead of OkHttp (ktor supports web)\n   - kotlinx.datetime instead of custom date handling\n3. Avoid platform.posix for file operations\n4. Test abstractions work without JVM assumptions\n\n**Example migration path:**\n```kotlin\n// Current: jvmAndroid (JVM-only)\napi(libs.jackson.module.kotlin)\n\n// Future: commonMain (all platforms)\napi(libs.kotlinx.serialization.json)\n```\n\n## Integration: When to Invoke Other Skills\n\n### Invoke gradle-expert\n\nTrigger gradle-expert skill when encountering:\n- Dependency conflicts (e.g., secp256k1-android vs secp256k1-jvm version mismatch)\n- Build errors related to source sets\n- Version catalog issues (libs.versions.toml)\n- \"Duplicate class\" errors\n- Performance/build time issues\n\n**Example trigger:**\n```\nError: Duplicate class found: fr.acinq.secp256k1.Secp256k1\n```\n→ Invoke gradle-expert for dependency conflict resolution.\n\n### Flags to Raise\n\n**Platform code in commonMain:**\n```kotlin\n// ❌ INCORRECT - Android API in commonMain\nexpect fun getContext(): Context  // Context is Android-only!\n```\n→ Flag: \"Android API in commonMain won't compile on other platforms\"\n\n**Duplicated business logic:**\n```kotlin\n// ❌ INCORRECT - Same logic in both\n// androidMain/.../CryptoUtils.kt\nfun validateSignature(...) { ... }\n\n// jvmMain/.../CryptoUtils.kt\nfun validateSignature(...) { ... }  // Duplicated!\n```\n→ Flag: \"Business logic duplicated, should be in commonMain or expect/actual\"\n\n**Reinventing wheel - suggest KMP alternatives:**\n- Custom date/time → kotlinx.datetime\n- OkHttp → ktor (supports web)\n- Jackson → kotlinx.serialization\n- Custom UUID → kotlinx.uuid (when stable)\n\n## Common Pitfalls\n\n### 1. Over-Abstraction\n**Problem:** Creating expect/actual for UI components\n```kotlin\n// ❌ BAD\nexpect fun NavigationComponent(...)\n```\n**Why:** Navigation paradigms too different (Activity vs Window)\n**Fix:** Keep platform-specific, accept duplication\n\n### 2. Under-Sharing\n**Problem:** Duplicating business logic across platforms\n```kotlin\n// ❌ BAD - duplicated in androidMain and jvmMain\nfun parseNostrEvent(json: String): Event { ... }\n```\n**Why:** Bug fixes need to be applied twice, tests duplicated\n**Fix:** Move to commonMain (pure Kotlin) or create expect/actual\n\n### 3. Leaky Abstractions\n**Problem:** Platform code in commonMain\n```kotlin\n// commonMain - ❌ BAD\nimport android.content.Context  // Won't compile on iOS!\n```\n**Fix:** Use expect/actual or dependency injection\n\n### 4. Premature Abstraction\n**Problem:** Creating expect/actual before second platform needs it\n```kotlin\n// ❌ BAD - only used on Android currently\nexpect fun showNotification(...)\n```\n**Why:** Wrong abstraction boundaries, wasted effort\n**Fix:** Wait until iOS actually needs it, then abstract\n\n### 5. Wrong Source Set\n**Problem:** JVM libraries in commonMain\n```kotlin\n// commonMain - ❌ BAD\nimport com.fasterxml.jackson.databind.ObjectMapper\n```\n**Why:** Jackson won't compile on iOS/web\n**Fix:** Move to jvmAndroid or migrate to kotlinx.serialization\n\n## Quick Reference\n\n| Code Type | Recommended Location | Reason |\n|-----------|---------------------|--------|\n| Pure Kotlin business logic | commonMain | Works everywhere |\n| Nostr protocol, NIPs | commonMain | Core logic, no platform APIs |\n| JVM libs (Jackson, OkHttp) | jvmAndroid | Android + Desktop only |\n| Crypto (varies by platform) | expect in commonMain, actual in platforms | Different security APIs per platform |\n| I/O, logging | expect in commonMain, actual in platforms | Platform implementations differ |\n| State (business logic) | commonMain or commons/jvmAndroid | Reusable StateFlow patterns |\n| **ViewModels** | **commons/commonMain/viewmodels/** | **StateFlow/SharedFlow + logic shareable, Compose MP lifecycle compatible** |\n| UI formatters (pure) | commons/commonMain | Reusable, no dependencies |\n| UI components (simple) | commons/commonMain | Cards, buttons, dialogs |\n| **Screen layouts** | **Platform-specific** | **Window vs Activity, sidebar vs bottom nav** |\n| Navigation | Platform-specific only | Activity vs Window too different |\n| Permissions | Platform-specific only | APIs incompatible |\n| Platform UX (menus, etc.) | Platform-specific only | Native feel required |\n\n## See Also\n\n- [references/abstraction-examples.md](references/abstraction-examples.md) - Good/bad abstraction examples with rationale\n- [references/source-set-hierarchy.md](references/source-set-hierarchy.md) - Visual hierarchy with Amethyst examples\n- [references/expect-actual-catalog.md](references/expect-actual-catalog.md) - All 24 expect/actual pairs with \"why abstracted\"\n- [references/target-compatibility.md](references/target-compatibility.md) - Platform constraints and future-proofing\n\n## Scripts\n\n- `scripts/validate-kmp-structure.sh` - Detect incorrect placements, validate source sets\n- `scripts/suggest-kmp-dependency.sh` - Suggest KMP library alternatives (ktor, kotlinx.serialization, etc.)\n\n<!-- File: .claude/skills/kotlin-concurrency-expert/SKILL.md (Tokens: ~1249 | Category: anthropic-skill) -->\n---\nname: kotlin-concurrency-expert\ndescription: Kotlin Coroutines review and remediation for Android. Use when asked to review concurrency usage, fix coroutine-related bugs, improve thread safety, or resolve lifecycle issues in Kotlin/Android code.\n---\n\n# Kotlin Concurrency Expert\n\n## Overview\n\nReview and fix Kotlin Coroutines issues in Android codebases by applying structured concurrency, lifecycle safety, proper scoping, and modern best practices with minimal behavior changes.\n\n## Workflow\n\n### 1. Triage the Issue\n\n- Capture the exact error, crash, or symptom (ANR, memory leak, race condition, incorrect state).\n- Check project coroutines setup: `kotlinx-coroutines-android` version, `lifecycle-runtime-ktx` version.\n- Identify the current scope context (`viewModelScope`, `lifecycleScope`, custom scope, or none).\n- Confirm whether the code is UI-bound (`Dispatchers.Main`) or intended to run off the main thread (`Dispatchers.IO`, `Dispatchers.Default`).\n- Verify Dispatcher injection patterns for testability.\n\n### 2. Apply the Smallest Safe Fix\n\nPrefer edits that preserve existing behavior while satisfying structured concurrency and lifecycle safety.\n\nCommon fixes:\n\n- **ANR / Main thread blocking**: Move heavy work to `withContext(Dispatchers.IO)` or `Dispatchers.Default`; ensure suspend functions are main-safe.\n- **Memory leaks / zombie coroutines**: Replace `GlobalScope` with a lifecycle-bound scope (`viewModelScope`, `lifecycleScope`, or injected `applicationScope`).\n- **Lifecycle collection issues**: Replace deprecated `launchWhenStarted` with `repeatOnLifecycle(Lifecycle.State.STARTED)`.\n- **State exposure**: Encapsulate `MutableStateFlow` / `MutableSharedFlow`; expose read-only `StateFlow` or `Flow`.\n- **CancellationException swallowing**: Ensure generic `catch (e: Exception)` blocks rethrow `CancellationException`.\n- **Non-cooperative cancellation**: Add `ensureActive()` or `yield()` in tight loops for cooperative cancellation.\n- **Callback APIs**: Convert listeners to `callbackFlow` with proper `awaitClose` cleanup.\n- **Hardcoded Dispatchers**: Inject `CoroutineDispatcher` via constructor for testability.\n\n## Critical Rules\n\n### Dispatcher Injection (Testability)\n\n```kotlin\n// CORRECT: Inject dispatcher\nclass UserRepository(\n    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO\n) {\n    suspend fun fetchUser() = withContext(ioDispatcher) { ... }\n}\n\n// INCORRECT: Hardcoded dispatcher\nclass UserRepository {\n    suspend fun fetchUser() = withContext(Dispatchers.IO) { ... }\n}\n```\n\n### Lifecycle-Aware Collection\n\n```kotlin\n// CORRECT: Use repeatOnLifecycle\nviewLifecycleOwner.lifecycleScope.launch {\n    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {\n        viewModel.uiState.collect { state -> updateUI(state) }\n    }\n}\n\n// INCORRECT: Direct collection (unsafe, deprecated)\nlifecycleScope.launchWhenStarted {\n    viewModel.uiState.collect { state -> updateUI(state) }\n}\n```\n\n### State Encapsulation\n\n```kotlin\n// CORRECT: Expose read-only StateFlow\nclass MyViewModel : ViewModel() {\n    private val _uiState = MutableStateFlow(UiState())\n    val uiState: StateFlow<UiState> = _uiState.asStateFlow()\n}\n\n// INCORRECT: Exposed mutable state\nclass MyViewModel : ViewModel() {\n    val uiState = MutableStateFlow(UiState()) // Leaks mutability\n}\n```\n\n### Exception Handling\n\n```kotlin\n// CORRECT: Rethrow CancellationException\ntry {\n    doSuspendWork()\n} catch (e: CancellationException) {\n    throw e // Must rethrow!\n} catch (e: Exception) {\n    handleError(e)\n}\n\n// INCORRECT: Swallows cancellation\ntry {\n    doSuspendWork()\n} catch (e: Exception) {\n    handleError(e) // CancellationException swallowed!\n}\n```\n\n### Cooperative Cancellation\n\n```kotlin\n// CORRECT: Check for cancellation in tight loops\nsuspend fun processLargeList(items: List<Item>) {\n    items.forEach { item ->\n        ensureActive() // Check cancellation\n        processItem(item)\n    }\n}\n\n// INCORRECT: Non-cooperative (ignores cancellation)\nsuspend fun processLargeList(items: List<Item>) {\n    items.forEach { item ->\n        processItem(item) // Never checks cancellation\n    }\n}\n```\n\n### Callback Conversion\n\n```kotlin\n// CORRECT: callbackFlow with awaitClose\nfun locationUpdates(): Flow<Location> = callbackFlow {\n    val listener = LocationListener { location ->\n        trySend(location)\n    }\n    locationManager.requestLocationUpdates(listener)\n    \n    awaitClose { locationManager.removeUpdates(listener) }\n}\n```\n\n## Scope Guidelines\n\n| Scope | Use When | Lifecycle |\n|-------|----------|-----------|\n| `viewModelScope` | ViewModel operations | Cleared with ViewModel |\n| `lifecycleScope` | UI operations in Activity/Fragment | Destroyed with lifecycle owner |\n| `repeatOnLifecycle` | Flow collection in UI | Started/Stopped with lifecycle state |\n| `applicationScope` (injected) | App-wide background work | Application lifetime |\n| `GlobalScope` | **NEVER USE** | Breaks structured concurrency |\n\n## Testing Pattern\n\n```kotlin\n@Test\nfun `loading data updates state`() = runTest {\n    val testDispatcher = StandardTestDispatcher(testScheduler)\n    val repository = FakeRepository()\n    val viewModel = MyViewModel(repository, testDispatcher)\n    \n    viewModel.loadData()\n    advanceUntilIdle()\n    \n    assertEquals(UiState.Success(data), viewModel.uiState.value)\n}\n```\n\n## Reference Material\n\n- [Kotlin Coroutines Best Practices](https://developer.android.com/kotlin/coroutines/coroutines-best-practices)\n- [StateFlow and SharedFlow](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow)\n- [repeatOnLifecycle API](https://developer.android.com/topic/libraries/architecture/coroutines#repeatOnLifecycle)\n\n<!-- File: .claude/skills/marketing-ideas/references/ideas-by-category.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/marketing-ideas/references/ideas-by-category.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/marketing-ideas/references/ideas-by-category.md)\n\n<!-- File: .claude/skills/marketing-ideas/SKILL.md (Tokens: ~1171 | Category: anthropic-skill) -->\n---\nname: marketing-ideas\nversion: 1.0.0\ndescription: \"When the user needs marketing ideas, inspiration, or strategies for their SaaS or software product. Also use when the user asks for 'marketing ideas,' 'growth ideas,' 'how to market,' 'marketing strategies,' 'marketing tactics,' 'ways to promote,' or 'ideas to grow.' This skill provides 139 proven marketing approaches organized by category.\"\n---\n\n# Marketing Ideas for SaaS\n\nYou are a marketing strategist with a library of 139 proven marketing ideas. Your goal is to help users find the right marketing strategies for their specific situation, stage, and resources.\n\n## How to Use This Skill\n\n**Check for product marketing context first:**\nIf `.claude/product-marketing-context.md` exists, read it before asking questions. Use that context and only ask for information not already covered or specific to this task.\n\nWhen asked for marketing ideas:\n1. Ask about their product, audience, and current stage if not clear\n2. Suggest 3-5 most relevant ideas based on their context\n3. Provide details on implementation for chosen ideas\n4. Consider their resources (time, budget, team size)\n\n---\n\n## Ideas by Category (Quick Reference)\n\n| Category | Ideas | Examples |\n|----------|-------|----------|\n| Content & SEO | 1-10 | Programmatic SEO, Glossary marketing, Content repurposing |\n| Competitor | 11-13 | Comparison pages, Marketing jiu-jitsu |\n| Free Tools | 14-22 | Calculators, Generators, Chrome extensions |\n| Paid Ads | 23-34 | LinkedIn, Google, Retargeting, Podcast ads |\n| Social & Community | 35-44 | LinkedIn audience, Reddit marketing, Short-form video |\n| Email | 45-53 | Founder emails, Onboarding sequences, Win-back |\n| Partnerships | 54-64 | Affiliate programs, Integration marketing, Newsletter swaps |\n| Events | 65-72 | Webinars, Conference speaking, Virtual summits |\n| PR & Media | 73-76 | Press coverage, Documentaries |\n| Launches | 77-86 | Product Hunt, Lifetime deals, Giveaways |\n| Product-Led | 87-96 | Viral loops, Powered-by marketing, Free migrations |\n| Content Formats | 97-109 | Podcasts, Courses, Annual reports, Year wraps |\n| Unconventional | 110-122 | Awards, Challenges, Guerrilla marketing |\n| Platforms | 123-130 | App marketplaces, Review sites, YouTube |\n| International | 131-132 | Expansion, Price localization |\n| Developer | 133-136 | DevRel, Certifications |\n| Audience-Specific | 137-139 | Referrals, Podcast tours, Customer language |\n\n**For the complete list with descriptions**: See [references/ideas-by-category.md](references/ideas-by-category.md)\n\n---\n\n## Implementation Tips\n\n### By Stage\n\n**Pre-launch:**\n- Waitlist referrals (#79)\n- Early access pricing (#81)\n- Product Hunt prep (#78)\n\n**Early stage:**\n- Content & SEO (#1-10)\n- Community (#35)\n- Founder-led sales (#47)\n\n**Growth stage:**\n- Paid acquisition (#23-34)\n- Partnerships (#54-64)\n- Events (#65-72)\n\n**Scale:**\n- Brand campaigns\n- International (#131-132)\n- Media acquisitions (#73)\n\n### By Budget\n\n**Free:**\n- Content & SEO\n- Community building\n- Social media\n- Comment marketing\n\n**Low budget:**\n- Targeted ads\n- Sponsorships\n- Free tools\n\n**Medium budget:**\n- Events\n- Partnerships\n- PR\n\n**High budget:**\n- Acquisitions\n- Conferences\n- Brand campaigns\n\n### By Timeline\n\n**Quick wins:**\n- Ads, email, social posts\n\n**Medium-term:**\n- Content, SEO, community\n\n**Long-term:**\n- Brand, thought leadership, platform effects\n\n---\n\n## Top Ideas by Use Case\n\n### Need Leads Fast\n- Google Ads (#31) - High-intent search\n- LinkedIn Ads (#28) - B2B targeting\n- Engineering as Marketing (#15) - Free tool lead gen\n\n### Building Authority\n- Conference Speaking (#70)\n- Book Marketing (#104)\n- Podcasts (#107)\n\n### Low Budget Growth\n- Easy Keyword Ranking (#1)\n- Reddit Marketing (#38)\n- Comment Marketing (#44)\n\n### Product-Led Growth\n- Viral Loops (#93)\n- Powered By Marketing (#87)\n- In-App Upsells (#91)\n\n### Enterprise Sales\n- Investor Marketing (#133)\n- Expert Networks (#57)\n- Conference Sponsorship (#72)\n\n---\n\n## Output Format\n\nWhen recommending ideas, provide for each:\n\n- **Idea name**: One-line description\n- **Why it fits**: Connection to their situation\n- **How to start**: First 2-3 implementation steps\n- **Expected outcome**: What success looks like\n- **Resources needed**: Time, budget, skills required\n\n---\n\n## Task-Specific Questions\n\n1. What's your current stage and main growth goal?\n2. What's your marketing budget and team size?\n3. What have you already tried that worked or didn't?\n4. What competitor tactics do you admire?\n\n---\n\n## Related Skills\n\n- **programmatic-seo**: For scaling SEO content (#4)\n- **competitor-alternatives**: For comparison pages (#11)\n- **email-sequence**: For email marketing tactics\n- **free-tool-strategy**: For engineering as marketing (#15)\n- **referral-program**: For viral growth (#93)\n\n<!-- File: .claude/skills/mobile-android-design/references/android-navigation.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/mobile-android-design/references/android-navigation.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/android-navigation.md)\n\n<!-- File: .claude/skills/mobile-android-design/references/compose-components.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/mobile-android-design/references/compose-components.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/compose-components.md)\n\n<!-- File: .claude/skills/mobile-android-design/references/material3-theming.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/mobile-android-design/references/material3-theming.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/maxrave-dev/SimpMusic/HEAD/.claude/skills/mobile-android-design/references/material3-theming.md)\n\n<!-- File: .claude/skills/mobile-android-design/SKILL.md (Tokens: ~2619 | Category: anthropic-skill) -->\n---\nname: mobile-android-design\ndescription: Master Material Design 3 and Jetpack Compose patterns for building native Android apps. Use when designing Android interfaces, implementing Compose UI, or following Google's Material Design guidelines.\n---\n\n# Android Mobile Design\n\nMaster Material Design 3 (Material You) and Jetpack Compose to build modern, adaptive Android applications that integrate seamlessly with the Android ecosystem.\n\n## When to Use This Skill\n\n- Designing Android app interfaces following Material Design 3\n- Building Jetpack Compose UI and layouts\n- Implementing Android navigation patterns (Navigation Compose)\n- Creating adaptive layouts for phones, tablets, and foldables\n- Using Material 3 theming with dynamic colors\n- Building accessible Android interfaces\n- Implementing Android-specific gestures and interactions\n- Designing for different screen configurations\n\n## Core Concepts\n\n### 1. Material Design 3 Principles\n\n**Personalization**: Dynamic color adapts UI to user's wallpaper\n**Accessibility**: Tonal palettes ensure sufficient color contrast\n**Large Screens**: Responsive layouts for tablets and foldables\n\n**Material Components:**\n\n- Cards, Buttons, FABs, Chips\n- Navigation (rail, drawer, bottom nav)\n- Text fields, Dialogs, Sheets\n- Lists, Menus, Progress indicators\n\n### 2. Jetpack Compose Layout System\n\n**Column and Row:**\n\n```kotlin\n// Vertical arrangement with alignment\nColumn(\n    modifier = Modifier.padding(16.dp),\n    verticalArrangement = Arrangement.spacedBy(12.dp),\n    horizontalAlignment = Alignment.Start\n) {\n    Text(\n        text = \"Title\",\n        style = MaterialTheme.typography.headlineSmall\n    )\n    Text(\n        text = \"Subtitle\",\n        style = MaterialTheme.typography.bodyMedium,\n        color = MaterialTheme.colorScheme.onSurfaceVariant\n    )\n}\n\n// Horizontal arrangement with weight\nRow(\n    modifier = Modifier.fillMaxWidth(),\n    horizontalArrangement = Arrangement.SpaceBetween,\n    verticalAlignment = Alignment.CenterVertically\n) {\n    Icon(Icons.Default.Star, contentDescription = null)\n    Text(\"Featured\")\n    Spacer(modifier = Modifier.weight(1f))\n    TextButton(onClick = {}) {\n        Text(\"View All\")\n    }\n}\n```\n\n**Lazy Lists and Grids:**\n\n```kotlin\n// Lazy column with sticky headers\nLazyColumn {\n    items.groupBy { it.category }.forEach { (category, categoryItems) ->\n        stickyHeader {\n            Text(\n                text = category,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(MaterialTheme.colorScheme.surface)\n                    .padding(16.dp),\n                style = MaterialTheme.typography.titleMedium\n            )\n        }\n        items(categoryItems) { item ->\n            ItemRow(item = item)\n        }\n    }\n}\n\n// Adaptive grid\nLazyVerticalGrid(\n    columns = GridCells.Adaptive(minSize = 150.dp),\n    contentPadding = PaddingValues(16.dp),\n    horizontalArrangement = Arrangement.spacedBy(12.dp),\n    verticalArrangement = Arrangement.spacedBy(12.dp)\n) {\n    items(items) { item ->\n        ItemCard(item = item)\n    }\n}\n```\n\n### 3. Navigation Patterns\n\n**Bottom Navigation:**\n\n```kotlin\n@Composable\nfun MainScreen() {\n    val navController = rememberNavController()\n\n    Scaffold(\n        bottomBar = {\n            NavigationBar {\n                val navBackStackEntry by navController.currentBackStackEntryAsState()\n                val currentDestination = navBackStackEntry?.destination\n\n                NavigationDestination.entries.forEach { destination ->\n                    NavigationBarItem(\n                        icon = { Icon(destination.icon, contentDescription = null) },\n                        label = { Text(destination.label) },\n                        selected = currentDestination?.hierarchy?.any {\n                            it.route == destination.route\n                        } == true,\n                        onClick = {\n                            navController.navigate(destination.route) {\n                                popUpTo(navController.graph.findStartDestination().id) {\n                                    saveState = true\n                                }\n                                launchSingleTop = true\n                                restoreState = true\n                            }\n                        }\n                    )\n                }\n            }\n        }\n    ) { innerPadding ->\n        NavHost(\n            navController = navController,\n            startDestination = NavigationDestination.Home.route,\n            modifier = Modifier.padding(innerPadding)\n        ) {\n            composable(NavigationDestination.Home.route) { HomeScreen() }\n            composable(NavigationDestination.Search.route) { SearchScreen() }\n            composable(NavigationDestination.Profile.route) { ProfileScreen() }\n        }\n    }\n}\n```\n\n**Navigation Drawer:**\n\n```kotlin\n@Composable\nfun DrawerNavigation() {\n    val drawerState = rememberDrawerState(DrawerValue.Closed)\n    val scope = rememberCoroutineScope()\n\n    ModalNavigationDrawer(\n        drawerState = drawerState,\n        drawerContent = {\n            ModalDrawerSheet {\n                Spacer(Modifier.height(12.dp))\n                Text(\n                    \"App Name\",\n                    modifier = Modifier.padding(16.dp),\n                    style = MaterialTheme.typography.titleLarge\n                )\n                HorizontalDivider()\n\n                NavigationDrawerItem(\n                    icon = { Icon(Icons.Default.Home, null) },\n                    label = { Text(\"Home\") },\n                    selected = true,\n                    onClick = { scope.launch { drawerState.close() } }\n                )\n                NavigationDrawerItem(\n                    icon = { Icon(Icons.Default.Settings, null) },\n                    label = { Text(\"Settings\") },\n                    selected = false,\n                    onClick = { }\n                )\n            }\n        }\n    ) {\n        Scaffold(\n            topBar = {\n                TopAppBar(\n                    title = { Text(\"Home\") },\n                    navigationIcon = {\n                        IconButton(onClick = { scope.launch { drawerState.open() } }) {\n                            Icon(Icons.Default.Menu, contentDescription = \"Menu\")\n                        }\n                    }\n                )\n            }\n        ) { innerPadding ->\n            Content(modifier = Modifier.padding(innerPadding))\n        }\n    }\n}\n```\n\n### 4. Material 3 Theming\n\n**Color Scheme:**\n\n```kotlin\n// Dynamic color (Android 12+)\nval dynamicColorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n    val context = LocalContext.current\n    if (darkTheme) dynamicDarkColorScheme(context)\n    else dynamicLightColorScheme(context)\n} else {\n    if (darkTheme) DarkColorScheme else LightColorScheme\n}\n\n// Custom color scheme\nprivate val LightColorScheme = lightColorScheme(\n    primary = Color(0xFF6750A4),\n    onPrimary = Color.White,\n    primaryContainer = Color(0xFFEADDFF),\n    onPrimaryContainer = Color(0xFF21005D),\n    secondary = Color(0xFF625B71),\n    onSecondary = Color.White,\n    tertiary = Color(0xFF7D5260),\n    onTertiary = Color.White,\n    surface = Color(0xFFFFFBFE),\n    onSurface = Color(0xFF1C1B1F),\n)\n```\n\n**Typography:**\n\n```kotlin\nval AppTypography = Typography(\n    displayLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 57.sp,\n        lineHeight = 64.sp\n    ),\n    headlineMedium = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 28.sp,\n        lineHeight = 36.sp\n    ),\n    titleLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 22.sp,\n        lineHeight = 28.sp\n    ),\n    bodyLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 16.sp,\n        lineHeight = 24.sp\n    ),\n    labelMedium = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Medium,\n        fontSize = 12.sp,\n        lineHeight = 16.sp\n    )\n)\n```\n\n### 5. Component Examples\n\n**Cards:**\n\n```kotlin\n@Composable\nfun FeatureCard(\n    title: String,\n    description: String,\n    imageUrl: String,\n    onClick: () -> Unit\n) {\n    Card(\n        onClick = onClick,\n        modifier = Modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(16.dp),\n        colors = CardDefaults.cardColors(\n            containerColor = MaterialTheme.colorScheme.surfaceVariant\n        )\n    ) {\n        Column {\n            AsyncImage(\n                model = imageUrl,\n                contentDescription = null,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .height(180.dp),\n                contentScale = ContentScale.Crop\n            )\n            Column(modifier = Modifier.padding(16.dp)) {\n                Text(\n                    text = title,\n                    style = MaterialTheme.typography.titleMedium\n                )\n                Spacer(modifier = Modifier.height(8.dp))\n                Text(\n                    text = description,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            }\n        }\n    }\n}\n```\n\n**Buttons:**\n\n```kotlin\n// Filled button (primary action)\nButton(onClick = { }) {\n    Text(\"Continue\")\n}\n\n// Filled tonal button (secondary action)\nFilledTonalButton(onClick = { }) {\n    Icon(Icons.Default.Add, null)\n    Spacer(Modifier.width(8.dp))\n    Text(\"Add Item\")\n}\n\n// Outlined button\nOutlinedButton(onClick = { }) {\n    Text(\"Cancel\")\n}\n\n// Text button\nTextButton(onClick = { }) {\n    Text(\"Learn More\")\n}\n\n// FAB\nFloatingActionButton(\n    onClick = { },\n    containerColor = MaterialTheme.colorScheme.primaryContainer,\n    contentColor = MaterialTheme.colorScheme.onPrimaryContainer\n) {\n    Icon(Icons.Default.Add, contentDescription = \"Add\")\n}\n```\n\n## Quick Start Component\n\n```kotlin\n@Composable\nfun ItemListCard(\n    item: Item,\n    onItemClick: () -> Unit,\n    modifier: Modifier = Modifier\n) {\n    Card(\n        onClick = onItemClick,\n        modifier = modifier.fillMaxWidth(),\n        shape = RoundedCornerShape(12.dp)\n    ) {\n        Row(\n            modifier = Modifier\n                .padding(16.dp)\n                .fillMaxWidth(),\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            Box(\n                modifier = Modifier\n                    .size(48.dp)\n                    .clip(CircleShape)\n                    .background(MaterialTheme.colorScheme.primaryContainer),\n                contentAlignment = Alignment.Center\n            ) {\n                Icon(\n                    imageVector = Icons.Default.Star,\n                    contentDescription = null,\n                    tint = MaterialTheme.colorScheme.onPrimaryContainer\n                )\n            }\n\n            Spacer(modifier = Modifier.width(16.dp))\n\n            Column(modifier = Modifier.weight(1f)) {\n                Text(\n                    text = item.title,\n                    style = MaterialTheme.typography.titleMedium\n                )\n                Text(\n                    text = item.subtitle,\n                    style = MaterialTheme.typography.bodyMedium,\n                    color = MaterialTheme.colorScheme.onSurfaceVariant\n                )\n            }\n\n            Icon(\n                imageVector = Icons.Default.ChevronRight,\n                contentDescription = null,\n                tint = MaterialTheme.colorScheme.onSurfaceVariant\n            )\n        }\n    }\n}\n```\n\n## Best Practices\n\n1. **Use Material Theme**: Access colors via `MaterialTheme.colorScheme` for automatic dark mode support\n2. **Support Dynamic Color**: Enable dynamic color on Android 12+ for personalization\n3. **Adaptive Layouts**: Use `WindowSizeClass` for responsive designs\n4. **Content Descriptions**: Add `contentDescription` to all interactive elements\n5. **Touch Targets**: Minimum 48dp touch targets for accessibility\n6. **State Hoisting**: Hoist state to make components reusable and testable\n7. **Remember Properly**: Use `remember` and `rememberSaveable` appropriately\n8. **Preview Annotations**: Add `@Preview` with different configurations\n\n## Common Issues\n\n- **Recomposition Issues**: Avoid passing unstable lambdas; use `remember`\n- **State Loss**: Use `rememberSaveable` for configuration changes\n- **Performance**: Use `LazyColumn` instead of `Column` for long lists\n- **Theme Leaks**: Ensure `MaterialTheme` wraps all composables\n- **Navigation Crashes**: Handle back press and deep links properly\n- **Memory Leaks**: Cancel coroutines in `DisposableEffect`\n\n## Resources\n\n- [Material Design 3](https://m3.material.io/)\n- [Jetpack Compose Documentation](https://developer.android.com/jetpack/compose)\n- [Compose Samples](https://github.com/android/compose-samples)\n- [Material 3 Compose](https://developer.android.com/jetpack/compose/designsystems/material3)\n\n<!-- File: .claude/skills/simpmusic-icons/SKILL.md (Tokens: ~2026 | Category: anthropic-skill) -->\n---\nname: simpmusic-icons\ndescription: Add or change an icon in SimpMusic. Every icon is a generated Material Symbols ImageVector under ui/icon, addressed as SimpIcons.<Name> — there is no material-icons dependency and no XML icon drawable. Use when adding a new icon, replacing one, or hitting ImageVector/Painter type errors.\n---\n\n# SimpMusic Icons\n\nIcons live in `composeApp/src/commonMain/kotlin/com/maxrave/simpmusic/ui/icon/`, one file per\nicon, each declaring `val SimpIcons.<Name>: ImageVector`.\n\nTwo things are deliberately absent and must not come back:\n- the `material-icons-core` / `material-icons-extended` dependency (`Icons.Rounded.*`)\n- XML icon drawables in `composeResources/drawable` used through `painterResource(...)`\n\n## Adding an icon\n\nGoogle's font service generates the Compose file directly — no conversion tool needed:\n\n```bash\ncurl -sfL --compressed \\\n  \"https://fonts.gstatic.com/render/v1/Material+Symbols+Rounded/24dp/<symbol_name>.kt?var=opsz,wght,FILL,GRAD,ROND@24,400,1,0,50\" \\\n  -o <PascalName>.kt\n```\n\nKeep these axes identical for every icon, or the set stops looking like one set:\n**Rounded, opsz 24, wght 400, GRAD 0, ROND 50**, `FILL=1`.\n\nUse `FILL=0` **only** for the \"off\" half of a state pair — `FavoriteBorder`,\n`AddCircleOutline`, `DownloadForOfflineOutlined`. At `FILL=1` the off state renders identically to\nthe on state and the distinction is lost.\n\nThen edit the downloaded file:\n\n1. `package com.example.test` → `package com.maxrave.simpmusic.ui.icon`\n2. `public val <symbol_name>: ImageVector` → `val SimpIcons.<PascalName>: ImageVector`\n3. Rename the backing field `_<symbol_name>` → `_<PascalName>`, and `name = \"<symbol_name>\"` → `\"<PascalName>\"`\n4. Never add `autoMirror = true` to `ImageVector.Builder(...)`: in right-to-left languages only text\n   flips, icons keep their shape (owner's rule, 2026-10-14). `ArrowForwardIos`, `QueueMusic`, `Sort`,\n   `TrendingUp`, `VolumeOff` and `VolumeUp` had it and drew mirrored until it was removed.\n5. Replace the closing `.build()` with `.buildSimpIcon()` (in `SimpIcons.kt`): it is `build()` plus\n   the fit into Material's 20×20 live area described under Traps. `SimpIconsFitTest` fails on a\n   file that still ends with `.build()`.\n\n## Using an icon\n\n```kotlin\nimport com.maxrave.simpmusic.ui.icon.PlayArrow   // per-icon import, required\nimport com.maxrave.simpmusic.ui.icon.SimpIcons\n\nIcon(SimpIcons.PlayArrow, contentDescription = null)\n```\n\nThe project's own icon buttons take an `ImageVector`:\n`RippleIconButton(imageVector = …)`, `LiquidGlassIconButton(imageVector = …)`,\n`ActionButton(icon = …)`.\n\n## Traps\n\n**Every icon needs its own import.** `val SimpIcons.X` is an *extension property*; importing the\n`SimpIcons` object alone leaves it unresolved. This is the same mechanism that lets R8 strip unused\nicons, so never \"simplify\" the set into a `map` or a `when` — that would ship all of them.\n\n**`ImageVector` is not a `Painter`.** `Icon` and `Image` have overloads for both, so those look\nfine. These do not, and need `rememberVectorPainter(SimpIcons.X)`:\n- `AsyncImage(placeholder = …, error = …)`\n- anything drawn inside a `DrawScope` (`with(painter) { draw(...) }`)\n- custom composables whose parameter is typed `Painter`\n\n**Do not text-replace blindly.** A regex swap of `Res.drawable.X` → `SimpIcons.X` produced 40\ncompile errors in one pass: it also hit `Painter` parameters, and it landed *inside* multi-line\n`painterResource(\\n  Res.drawable.X,\\n)` calls, leaving the syntactically valid but wrong\n`painterResource(SimpIcons.X)`.\n\n**Do not replace an icon whose colour carries meaning.** These stay as resources:\n\n| File | Why |\n|---|---|\n| `baseline_downloaded.xml` | `#FF00A0CB` — the blue *is* the \"downloaded\" state |\n| `baseline_favorite_24.xml` | `#D10000` — the red *is* the \"liked\" state |\n| `mono.xml`, `monochrome.xml` | SimpMusic logos (`#0AEAF8` / black) |\n| `app_icon.png`, `circle_app_icon.png`, `holder*.png` | bitmaps, not icons |\n\nIf a state needs that colour with a shared symbol, pass it explicitly — e.g. `ActionButton`'s\n`iconColor = Color(0xFF00A0CB)` for the downloaded state.\n\n**Verify the symbol name exists** before assuming a mapping, against\n`google/material-design-icons` → `variablefont/MaterialSymbolsRounded[FILL,GRAD,opsz,wght].codepoints`.\nLegacy names such as `favorite_border`, `add_circle_outline` and `thumb_up_alt` *do* still exist;\n`person_add_alt_1` does not (use `person_add`).\n\n**Glyphs that spill past Material's 20×20 live area are fitted into it (2026-10-18).** Material's\nicon grid keeps a glyph inside the centred 20×20 square of the 24 box, with 2dp of padding, and\nbalances the glyphs inside it by keyline shapes of about equal area. 24 of our 111 Symbols spill\ninto that padding anyway: `Lyrics`' note reaches the box edge, `ThumbsUpDown` spans the\nfull width. At one size those read bigger than everything beside them. `buildSimpIcon()` measures\nhow far each glyph reaches from the centre along either axis (walking its path nodes, 16 samples\nper curve) and shrinks one that passes 10 of 24 about the centre until it fits: `Lyrics` and\n`ThumbsUpDown` ×0.83, the other 21 ×0.86–0.996. A glyph inside the square is left exactly as Google\ndrew it, nothing is enlarged, and anything it cannot measure (an arc, a group, a fit tighter than\n×0.6) keeps the glyph as generated and logs under `SimpIcons`. `SimpIconsFitTest` (jvmTest) fails if\na file skips `buildSimpIcon()`, an icon outside `KEEP_AS_DRAWN` ends up outside the square, or one in\nit gets rebuilt.\n- `Groups` is named in `KEEP_AS_DRAWN` (in `SimpIcons.kt`) and keeps Google's drawing (2026-10-20).\n  It spills only sideways, so fitting its 24 width also cut its height, which was never too tall,\n  from 12 to 10 of 24, and it read shorter than every icon beside it. Five more spill only sideways\n  and lose a little height to the fit (`PeopleAlt`, `PersonAdd`, `Insights` 9%, `AutoGraph` 6%,\n  `AccessAlarm` 2%); they still end 14.5 of 24 tall or more, like Symbols Google draws at that\n  height, so they stay fitted (owner's call).\n- Do not fit glyphs into a circle instead. That was tried first: a circle judges a glyph by its one\n  farthest point, so `Lyrics` shrank to ×0.72 to pull its note's tip inside and then read far\n  smaller than `QueueMusic` beside it.\n\n**A small round button cuts a glyph that fills the live area.** `IconButton` clips its content to\nits `shape` (a circle by default), and a button sized 24dp makes that a 24dp circle, 12dp from the\ncentre. A glyph that fills the 20×20 square reaches 14.1 at its corners — `comment` reaches 13.3 —\nso its corners are cut while round or open glyphs beside it are not. The icon file is not the\nproblem; the button is. Check the farthest glyph point before putting an icon in one: Classic's\nComments button clips to 28dp with `requiredSize(28.dp)` inside the same 24dp slot.\n\n**Icons in one row take one size.** Tuning each glyph by eye is what made the Apple Music row read\nbig-small (23 and 24dp beside 22dp dock buttons, the title-row star 32dp beside 24dp glyphs). Only a\ndeliberate hierarchy, such as a Play button over Previous/Next, gets a different size.\n\n**The response is gzipped** even when the request asks for `identity` — `curl --compressed`, or\ndecompress by magic bytes (`\\x1f\\x8b`) when fetching from a script.\n\n<!-- File: .claude/skills/xml-to-compose-migration/SKILL.md (Tokens: ~2445 | Category: anthropic-skill) -->\n---\nname: xml-to-compose-migration\ndescription: Convert Android XML layouts to Jetpack Compose. Use when asked to migrate Views to Compose, convert XML to Composables, or modernize UI from View system to Compose.\n---\n\n# XML to Compose Migration\n\n## Overview\n\nSystematically convert Android XML layouts to idiomatic Jetpack Compose, preserving functionality while embracing Compose patterns. This skill covers layout mapping, state migration, and incremental adoption strategies.\n\n## Workflow\n\n### 1. Analyze the XML Layout\n\n- Identify the root layout type (`ConstraintLayout`, `LinearLayout`, `FrameLayout`, etc.).\n- List all View widgets and their key attributes.\n- Map data binding expressions (`@{}`) or view binding references.\n- Identify custom views that need special handling.\n- Note any `include`, `merge`, or `ViewStub` usage.\n\n### 2. Plan the Migration\n\n- Decide: **Full rewrite** or **incremental migration** (using `ComposeView`/`AndroidView`).\n- Identify state sources (ViewModel, LiveData, savedInstanceState).\n- List reusable components to extract as separate Composables.\n- Plan navigation integration if using Navigation component.\n\n### 3. Convert Layouts\n\nApply the layout mapping table below to convert each View to its Compose equivalent.\n\n### 4. Migrate State\n\n- Convert `LiveData` observation to `StateFlow` collection or `observeAsState()`.\n- Replace `findViewById` / ViewBinding with Compose state.\n- Convert click listeners to lambda parameters.\n\n### 5. Test and Verify\n\n- Compare visual output between XML and Compose versions.\n- Test accessibility (content descriptions, touch targets).\n- Verify state preservation across configuration changes.\n\n---\n\n## Layout Mapping Reference\n\n### Container Layouts\n\n| XML Layout | Compose Equivalent | Notes |\n|------------|-------------------|-------|\n| `LinearLayout (vertical)` | `Column` | Use `Arrangement` and `Alignment` |\n| `LinearLayout (horizontal)` | `Row` | Use `Arrangement` and `Alignment` |\n| `FrameLayout` | `Box` | Children stack on top of each other |\n| `ConstraintLayout` | `ConstraintLayout` (Compose) | Use `createRefs()` and `constrainAs` |\n| `RelativeLayout` | `Box` or `ConstraintLayout` | Prefer Box for simple overlap |\n| `ScrollView` | `Column` + `Modifier.verticalScroll()` | Or use `LazyColumn` for lists |\n| `HorizontalScrollView` | `Row` + `Modifier.horizontalScroll()` | Or use `LazyRow` for lists |\n| `RecyclerView` | `LazyColumn` / `LazyRow` / `LazyGrid` | Most common migration |\n| `ViewPager2` | `HorizontalPager` | From accompanist or Compose Foundation |\n| `CoordinatorLayout` | Custom + `Scaffold` | Use `TopAppBar` with scroll behavior |\n| `NestedScrollView` | `Column` + `Modifier.verticalScroll()` | Prefer Lazy variants |\n\n### Common Widgets\n\n| XML Widget | Compose Equivalent | Notes |\n|------------|-------------------|-------|\n| `TextView` | `Text` | Use `style` → `TextStyle` |\n| `EditText` | `TextField` / `OutlinedTextField` | Requires state hoisting |\n| `Button` | `Button` | Use `onClick` lambda |\n| `ImageView` | `Image` | Use `painterResource()` or Coil |\n| `ImageButton` | `IconButton` | Use `Icon` inside |\n| `CheckBox` | `Checkbox` | Requires `checked` + `onCheckedChange` |\n| `RadioButton` | `RadioButton` | Use with `Row` for groups |\n| `Switch` | `Switch` | Requires state hoisting |\n| `ProgressBar (circular)` | `CircularProgressIndicator` | |\n| `ProgressBar (horizontal)` | `LinearProgressIndicator` | |\n| `SeekBar` | `Slider` | Requires state hoisting |\n| `Spinner` | `DropdownMenu` + `ExposedDropdownMenuBox` | More complex pattern |\n| `CardView` | `Card` | From Material 3 |\n| `Toolbar` | `TopAppBar` | Use inside `Scaffold` |\n| `BottomNavigationView` | `NavigationBar` | Material 3 |\n| `FloatingActionButton` | `FloatingActionButton` | Use inside `Scaffold` |\n| `Divider` | `HorizontalDivider` / `VerticalDivider` | |\n| `Space` | `Spacer` | Use `Modifier.size()` |\n\n### Attribute Mapping\n\n| XML Attribute | Compose Modifier/Property |\n|---------------|--------------------------|\n| `android:layout_width=\"match_parent\"` | `Modifier.fillMaxWidth()` |\n| `android:layout_height=\"match_parent\"` | `Modifier.fillMaxHeight()` |\n| `android:layout_width=\"wrap_content\"` | `Modifier.wrapContentWidth()` (usually implicit) |\n| `android:layout_weight` | `Modifier.weight(1f)` |\n| `android:padding` | `Modifier.padding()` |\n| `android:layout_margin` | `Modifier.padding()` on parent, or use `Arrangement.spacedBy()` |\n| `android:background` | `Modifier.background()` |\n| `android:visibility=\"gone\"` | Conditional composition (don't emit) |\n| `android:visibility=\"invisible\"` | `Modifier.alpha(0f)` (keeps space) |\n| `android:clickable` | `Modifier.clickable { }` |\n| `android:contentDescription` | `Modifier.semantics { contentDescription = \"\" }` |\n| `android:elevation` | `Modifier.shadow()` or component's `elevation` param |\n| `android:alpha` | `Modifier.alpha()` |\n| `android:rotation` | `Modifier.rotate()` |\n| `android:scaleX/Y` | `Modifier.scale()` |\n| `android:gravity` | `Alignment` parameter or `Arrangement` |\n| `android:layout_gravity` | `Modifier.align()` |\n\n---\n\n## Common Patterns\n\n### LinearLayout with Weights\n\n```xml\n<!-- XML -->\n<LinearLayout android:orientation=\"horizontal\">\n    <View android:layout_weight=\"1\" />\n    <View android:layout_weight=\"2\" />\n</LinearLayout>\n```\n\n```kotlin\n// Compose\nRow(modifier = Modifier.fillMaxWidth()) {\n    Box(modifier = Modifier.weight(1f))\n    Box(modifier = Modifier.weight(2f))\n}\n```\n\n### RecyclerView to LazyColumn\n\n```xml\n<!-- XML -->\n<androidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/recyclerView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\" />\n```\n\n```kotlin\n// Compose\nLazyColumn(modifier = Modifier.fillMaxSize()) {\n    items(items, key = { it.id }) { item ->\n        ItemRow(item = item, onClick = { onItemClick(item) })\n    }\n}\n```\n\n### EditText with Two-Way Binding\n\n```xml\n<!-- XML with Data Binding -->\n<EditText\n    android:text=\"@={viewModel.username}\"\n    android:hint=\"@string/username_hint\" />\n```\n\n```kotlin\n// Compose\nval username by viewModel.username.collectAsState()\n\nOutlinedTextField(\n    value = username,\n    onValueChange = { viewModel.updateUsername(it) },\n    label = { Text(stringResource(R.string.username_hint)) },\n    modifier = Modifier.fillMaxWidth()\n)\n```\n\n### ConstraintLayout Migration\n\n```xml\n<!-- XML -->\n<androidx.constraintlayout.widget.ConstraintLayout>\n    <TextView\n        android:id=\"@+id/title\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\" />\n    <TextView\n        android:id=\"@+id/subtitle\"\n        app:layout_constraintTop_toBottomOf=\"@id/title\"\n        app:layout_constraintStart_toStartOf=\"@id/title\" />\n</androidx.constraintlayout.widget.ConstraintLayout>\n```\n\n```kotlin\n// Compose\nConstraintLayout(modifier = Modifier.fillMaxWidth()) {\n    val (title, subtitle) = createRefs()\n    \n    Text(\n        text = \"Title\",\n        modifier = Modifier.constrainAs(title) {\n            top.linkTo(parent.top)\n            start.linkTo(parent.start)\n        }\n    )\n    Text(\n        text = \"Subtitle\", \n        modifier = Modifier.constrainAs(subtitle) {\n            top.linkTo(title.bottom)\n            start.linkTo(title.start)\n        }\n    )\n}\n```\n\n### Include / Merge → Extract Composable\n\n```xml\n<!-- XML: layout_header.xml -->\n<merge>\n    <ImageView android:id=\"@+id/avatar\" />\n    <TextView android:id=\"@+id/name\" />\n</merge>\n\n<!-- Usage -->\n<include layout=\"@layout/layout_header\" />\n```\n\n```kotlin\n// Compose: Extract as a reusable Composable\n@Composable\nfun HeaderSection(\n    avatarUrl: String,\n    name: String,\n    modifier: Modifier = Modifier\n) {\n    Row(modifier = modifier) {\n        AsyncImage(model = avatarUrl, contentDescription = null)\n        Text(text = name)\n    }\n}\n\n// Usage\nHeaderSection(avatarUrl = user.avatar, name = user.name)\n```\n\n---\n\n## Incremental Migration (Interop)\n\n### Embedding Compose in XML\n\n```xml\n<!-- In your XML layout -->\n<androidx.compose.ui.platform.ComposeView\n    android:id=\"@+id/compose_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\" />\n```\n\n```kotlin\n// In Fragment/Activity\nbinding.composeView.setContent {\n    MaterialTheme {\n        MyComposable()\n    }\n}\n```\n\n### Embedding XML Views in Compose\n\n```kotlin\n// Use AndroidView for Views that don't have Compose equivalents\n@Composable\nfun MapViewComposable(modifier: Modifier = Modifier) {\n    AndroidView(\n        factory = { context ->\n            MapView(context).apply {\n                // Initialize the view\n            }\n        },\n        update = { mapView ->\n            // Update the view when state changes\n        },\n        modifier = modifier\n    )\n}\n```\n\n---\n\n## State Migration\n\n### LiveData to Compose\n\n```kotlin\n// Before: Observing in Fragment\nviewModel.uiState.observe(viewLifecycleOwner) { state ->\n    binding.title.text = state.title\n}\n\n// After: Collecting in Compose\n@Composable\nfun MyScreen(viewModel: MyViewModel = hiltViewModel()) {\n    val uiState by viewModel.uiState.collectAsStateWithLifecycle()\n    \n    Text(text = uiState.title)\n}\n```\n\n### Click Listeners\n\n```kotlin\n// Before: XML + setOnClickListener\nbinding.submitButton.setOnClickListener {\n    viewModel.submit()\n}\n\n// After: Lambda in Compose\nButton(onClick = { viewModel.submit() }) {\n    Text(\"Submit\")\n}\n```\n\n---\n\n## Checklist\n\n- [ ] All layouts converted (no `include` or `merge` left)\n- [ ] State hoisted properly (no internal mutable state for user input)\n- [ ] Click handlers converted to lambdas\n- [ ] RecyclerView adapters removed (using LazyColumn/LazyRow)\n- [ ] ViewBinding/DataBinding removed\n- [ ] Navigation integrated (NavHost or interop)\n- [ ] Theming applied (MaterialTheme)\n- [ ] Accessibility preserved (content descriptions, touch targets)\n- [ ] Preview annotations added for development\n- [ ] Old XML files deleted\n\n## References\n\n- [Interoperability APIs](https://developer.android.com/develop/ui/compose/migrate/interoperability-apis)\n- [Migration Strategy](https://developer.android.com/develop/ui/compose/migrate/strategy)\n- [Compose and Views side by side](https://developer.android.com/develop/ui/compose/migrate)\n\n<!-- ... +36 more modular skills available via /api/skills/maxrave-dev/SimpMusic?path={path} -->\n</yakaai_skills>"}