diff --git a/docs/plans/2026-03-18-media3-android-auto-design.md b/docs/plans/2026-03-18-media3-android-auto-design.md
new file mode 100644
index 0000000..d194c7f
--- /dev/null
+++ b/docs/plans/2026-03-18-media3-android-auto-design.md
@@ -0,0 +1,176 @@
+# Media3 Migration + Android Auto Support
+
+## Goals
+
+1. Proper system media notification with metadata, album art, and transport controls (Play/Stop + Seek to Live)
+2. Android Auto compatibility — app registers as a media source with a browsable station catalog
+
+## Approach
+
+Migrate from legacy `MediaSessionCompat` + manual `NotificationHelper` to **Media3 `MediaLibraryService`** with a thin `Player` adapter wrapping the existing custom audio engine. The custom audio pipeline (OkHttp → IcyParser → MediaCodec → AudioTrack) remains completely untouched.
+
+## Architecture
+
+```
+RadioController ──intent──▶ RadioPlaybackService (MediaLibraryService)
+ │
+ ├── RadioPlayerAdapter (implements Player)
+ │ └── delegates to AudioEngine (untouched)
+ │
+ ├── MediaLibrarySession
+ │ ├── auto-publishes metadata/state/art to OS
+ │ ├── auto-manages notification (Play/Stop/SeekLive)
+ │ └── serves browse tree for Android Auto
+ │
+ └── AudioEngine (unchanged custom pipeline)
+```
+
+## RadioPlayerAdapter
+
+A thin facade implementing `androidx.media3.common.Player` that maps the small radio-relevant command surface to existing logic. It does NOT produce or route audio.
+
+### Supported commands
+
+- `COMMAND_PLAY_PAUSE`
+- `COMMAND_STOP`
+- `COMMAND_GET_CURRENT_MEDIA_ITEM`
+- `COMMAND_GET_MEDIA_ITEMS_METADATA`
+- Custom `SessionCommand("SEEK_TO_LIVE")` — exposed as a skip-forward button
+
+### State mapping
+
+| PlaybackState (existing) | Media3 Player.STATE_* | isPlaying |
+|---|---|---|
+| Idle | STATE_IDLE | false |
+| Connecting | STATE_BUFFERING | false |
+| Playing | STATE_READY | true |
+| Paused | STATE_READY | false |
+| Reconnecting | STATE_BUFFERING | false |
+
+### Command mapping
+
+- `play()` → `RadioController.play(currentStation)`
+- `stop()` → `RadioController.stop()`
+- `pause()` → `RadioController.stop()` (radio streams don't pause)
+- Metadata changes from ICY events update `MediaMetadata` on the adapter, which auto-propagates to notification/lockscreen/Auto/Bluetooth
+
+## RadioPlaybackService Changes
+
+Converts from `LifecycleService` to `MediaLibraryService`.
+
+### Stays the same
+
+- Intent-based ACTION_PLAY/STOP/PAUSE/SEEK_LIVE control flow
+- AudioEngine lifecycle (create, start, stop, event collection)
+- Wake lock + WiFi lock management
+- Reconnect loop with backoff
+- Connection span / listening session DB tracking
+- StreamResolver URL resolution
+
+### Changes
+
+- `LifecycleService` → `MediaLibraryService`
+- `MediaSessionCompat` → `MediaLibrarySession` (built with `RadioPlayerAdapter`)
+- `onGetSession()` returns the `MediaLibrarySession`
+- `NotificationHelper` deleted — Media3 auto-generates notifications from session state
+- `startForeground()` calls removed — `MediaLibraryService` handles foreground lifecycle
+- On ICY metadata change, adapter's `MediaMetadata` updates → auto-propagates everywhere
+- Album art: `AlbumArtResolver` resolves artwork → set on `MediaMetadata.artworkUri`
+
+### Notification layout (auto-managed)
+
+- Compact: Play/Stop toggle + Seek to Live (custom command button)
+- Expanded: same + album art + station name + track info
+- Channel importance: LOW
+
+## Android Auto Browse Tree
+
+```
+[ROOT]
+ ├── Playlist: "SomaFM"
+ │ ├── Groove Salad
+ │ ├── Drone Zone
+ │ └── ...
+ ├── Playlist: "My Favorites"
+ │ ├── Station A
+ │ └── Station B
+ └── Unsorted (if any stations have no playlist)
+ ├── Station X
+ └── Station Y
+```
+
+### Callbacks
+
+- `onGetLibraryRoot()` → root MediaItem (browsable)
+- `onGetChildren(rootId)` → playlists + unsorted folder (browsable MediaItems)
+- `onGetChildren(playlistId)` → stations in playlist (playable MediaItems)
+- `onGetChildren("unsorted")` → stations with no playlist
+- `onSetMediaItem()` / `onAddMediaItems()` → parse mediaId, look up station, call `RadioController.play()`
+
+### Media IDs
+
+- Root: `"root"`
+- Playlist folder: `"playlist:{id}"`
+- Unsorted folder: `"unsorted"`
+- Station item: `"station:{id}"`
+
+### Data freshness
+
+DAO `Flow` methods use `.first()` for snapshots. Updates visible on next navigation (acceptable for a radio catalog).
+
+## Dependencies
+
+### Add
+
+```toml
+media3 = "1.6.0"
+media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }
+media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "media3" }
+```
+
+### Remove
+
+- `media-session` (`androidx.media:media:1.7.1`)
+
+No `media3-exoplayer` needed — custom audio engine stays.
+
+## Manifest Changes
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+## New File: `res/xml/automotive_app.xml`
+
+```xml
+
+
+
+
+```
+
+## Files Affected
+
+| File | Action |
+|---|---|
+| `libs.versions.toml` | Add media3 version + libraries, remove old media |
+| `app/build.gradle.kts` | Swap dependencies |
+| `AndroidManifest.xml` | Update service declaration, add Auto meta-data |
+| `res/xml/automotive_app.xml` | New file |
+| `RadioPlayerAdapter.kt` | New file — Player facade |
+| `RadioPlaybackService.kt` | Rewrite to extend MediaLibraryService |
+| `NotificationHelper.kt` | Delete |
+| `RadioController.kt` | Minor — may need adapter reference for metadata updates |
+| `RadioApplication.kt` | Minor — remove NotificationHelper if referenced |