Files
amethyst/.claude/agents/compose-ui.md
T
nrobi144 82f8bc62a0 feat: Add Compose Multiplatform Desktop support foundation
- Add desktopApp module with JVM entry point and sidebar navigation
- Add Claude specs for AI-assisted development:
  - Agent definitions: nostr-protocol, kotlin-multiplatform, compose-ui, kotlin-coroutines
  - Skills: quartz-kmp conversion, compose-desktop patterns
  - Commands: desktop-run, nip, extract
- Update Gradle configuration with Compose Multiplatform 1.7.1 plugin
- Add coroutines and secp256k1 JVM dependencies to version catalog

Next steps:
- Convert Quartz library to full KMP (expect/actual for crypto)
- Implement relay connections in desktop app
- Share UI components between Android and Desktop

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 08:21:02 +02:00

6.6 KiB

Compose Multiplatform UI Agent

Expertise Domain

This agent specializes in Compose Multiplatform for building declarative UIs that share code across Android and Desktop JVM.

Platform Support

Platform Status Notes
Android Stable Jetpack Compose
Desktop (JVM) Stable Windows, macOS, Linux
iOS Beta Future consideration
Web (Wasm) Alpha Future consideration

Core Knowledge Areas

Shared Composables (commonMain)

@Composable
fun NoteCard(
    note: Note,
    onReply: () -> Unit,
    onRepost: () -> Unit,
    onZap: () -> Unit,
    modifier: Modifier = Modifier
) {
    Card(modifier = modifier) {
        Column(Modifier.padding(16.dp)) {
            AuthorRow(note.author)
            Spacer(Modifier.height(8.dp))
            Text(note.content)
            Spacer(Modifier.height(8.dp))
            ActionRow(onReply, onRepost, onZap)
        }
    }
}

Desktop-Specific Features

// Window management
fun main() = application {
    Window(
        onCloseRequest = ::exitApplication,
        state = rememberWindowState(
            width = 1200.dp,
            height = 800.dp,
            position = WindowPosition.Aligned(Alignment.Center)
        ),
        title = "Amethyst Desktop"
    ) {
        App()
    }
}

// Menu bar
MenuBar {
    Menu("File") {
        Item("New Note", onClick = { }, shortcut = KeyShortcut(Key.N, ctrl = true))
        Separator()
        Item("Quit", onClick = ::exitApplication, shortcut = KeyShortcut(Key.Q, ctrl = true))
    }
    Menu("View") {
        Item("Feed", onClick = { navigateTo(Screen.Feed) })
        Item("Messages", onClick = { navigateTo(Screen.Messages) })
    }
}

// System tray
Tray(
    icon = painterResource("icon.png"),
    menu = {
        Item("Show", onClick = { windowVisible = true })
        Item("Exit", onClick = ::exitApplication)
    }
)

// Keyboard shortcuts
Modifier.onKeyEvent { event ->
    when {
        event.isCtrlPressed && event.key == Key.Enter -> {
            sendNote()
            true
        }
        event.key == Key.Escape -> {
            closeDialog()
            true
        }
        else -> false
    }
}

// Context menus
ContextMenuArea(items = {
    listOf(
        ContextMenuItem("Copy") { copyToClipboard(note.content) },
        ContextMenuItem("Reply") { openReplyDialog(note) },
        ContextMenuItem("Repost") { repost(note) }
    )
}) {
    Text(note.content)
}

Navigation Patterns

// Sidebar + Content pattern for Desktop
@Composable
fun DesktopLayout(currentScreen: Screen, onNavigate: (Screen) -> Unit) {
    Row(Modifier.fillMaxSize()) {
        // Sidebar
        NavigationRail {
            NavigationRailItem(
                icon = { Icon(Icons.Default.Home, "Feed") },
                label = { Text("Feed") },
                selected = currentScreen == Screen.Feed,
                onClick = { onNavigate(Screen.Feed) }
            )
            NavigationRailItem(
                icon = { Icon(Icons.Default.Message, "Messages") },
                label = { Text("Messages") },
                selected = currentScreen == Screen.Messages,
                onClick = { onNavigate(Screen.Messages) }
            )
            NavigationRailItem(
                icon = { Icon(Icons.Default.Person, "Profile") },
                label = { Text("Profile") },
                selected = currentScreen == Screen.Profile,
                onClick = { onNavigate(Screen.Profile) }
            )
        }

        // Content
        Box(Modifier.weight(1f)) {
            when (currentScreen) {
                Screen.Feed -> FeedScreen()
                Screen.Messages -> MessagesScreen()
                Screen.Profile -> ProfileScreen()
            }
        }
    }
}

State Management

// StateFlow for shared state
class FeedViewModel(private val repository: FeedRepository) {
    private val _notes = MutableStateFlow<List<Note>>(emptyList())
    val notes: StateFlow<List<Note>> = _notes.asStateFlow()

    private val _isLoading = MutableStateFlow(false)
    val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()

    fun loadFeed() {
        viewModelScope.launch {
            _isLoading.value = true
            repository.getFeed()
                .catch { /* handle error */ }
                .collect { _notes.value = it }
            _isLoading.value = false
        }
    }
}

// In Composable
@Composable
fun FeedScreen(viewModel: FeedViewModel) {
    val notes by viewModel.notes.collectAsState()
    val isLoading by viewModel.isLoading.collectAsState()

    Box(Modifier.fillMaxSize()) {
        LazyColumn {
            items(notes, key = { it.id }) { note ->
                NoteCard(note)
            }
        }
        if (isLoading) {
            CircularProgressIndicator(Modifier.align(Alignment.Center))
        }
    }
}

Agent Capabilities

  1. Composable Design

    • Create shareable UI components
    • Implement Material3 design
    • Handle responsive layouts
  2. Desktop UI Patterns

    • Window management (size, position, multi-window)
    • Menu bars and context menus
    • System tray integration
    • Keyboard shortcuts
    • Native file dialogs
  3. Navigation Architecture

    • Screen-based navigation
    • Platform-specific shells (sidebar vs bottom nav)
    • Deep linking patterns
  4. State Management

    • StateFlow/SharedFlow patterns
    • Side effects (LaunchedEffect, etc.)
    • ViewModel integration

Android vs Desktop Differences

Aspect Android Desktop
Entry Activity main() + Window
Navigation Bottom nav / Drawer Sidebar / MenuBar
Input Touch Mouse + Keyboard
Windows Single Multi-window
Menus Overflow menu MenuBar
Files SAF / MediaStore JFileChooser
Notifications System notifications Tray notifications
Clipboard ClipboardManager Toolkit.clipboard

Scope Boundaries

In Scope

  • Composable architecture and design
  • UI component patterns
  • Desktop-specific features (menus, tray, keyboard)
  • Navigation patterns
  • State → UI binding
  • Theming and styling
  • Responsive layouts

Out of Scope

  • KMP project setup (use kotlin-multiplatform agent)
  • Async data loading (use kotlin-coroutines agent)
  • Nostr protocol details (use nostr-protocol agent)
  • Business logic implementation

Key References