This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
mjdev-desktop is a Kotlin Compose Multiplatform desktop environment targeting Linux (primary), Android, and Windows. It has two runtime layers:
shared + desktopApp) — the full desktop UI built with Jetpack Compose Desktop. This is the primary development surface.compositor) — a Kotlin/Native binary (mjdevc) wrapping wlroots via a thin C shim. Only handles seat/input/focus/surface layering — all desktop policy lives in the Kotlin shell.# Run desktop app (JVM, no compositor needed)
./gradlew :desktopApp:run
# Run desktop inside a nested Wayland compositor (needs an X11/Wayland host session)
./gradlew :compositor:runNestedDesktop
# Build all distributables (deb, rpm, AppImage, apk) → releases/
./gradlew buildAll
# Build and install the full desktop (deb + compositor + session) via pkexec dialog
./gradlew :compositor:installDesktop
# Build desktop deb only
./gradlew :desktopApp:packageReleaseDeb
# Lint (report only — never breaks the build)
./gradlew ktlintCheck
# Auto-format
./gradlew :shared:ktlintFormat
# Dependency update report → reports/dependencies/
./gradlew :shared:dependencyUpdates
# Build ISO (needs debootstrap / squashfs-tools / xorriso / grub tools + root/pkexec)
./gradlew makeIso
# Run ISO in QEMU headlessly (no root needed)
./gradlew runIsoQemu
# or directly:
./run-iso-qemu.sh
# Android APK
./gradlew :androidApp:assembleRelease
ktlintFormat + ktlintCheck run automatically after every build task via the postBuildCodeCheck hook.
| Module | Language | Purpose |
|---|---|---|
shared |
Kotlin Multiplatform (JVM + Android) | All UI components, managers, business logic |
desktopApp |
Kotlin JVM | Entry point (org.mjdev.desktop.main.MainKt), compose desktop wiring |
androidApp |
Kotlin Android | Entry point for Android target |
compositor |
Kotlin/Native (linuxX64) + C | Wayland compositor binary mjdevc |
buildSrc |
Kotlin DSL | Custom Gradle tasks: PackageFullDebTask, PackageAppImageTask, EnsureAppImageToolTask, AiAgentPlugin |
Build file naming convention: each module uses <name>.gradle.kts (not build.gradle.kts).
IDesktopContext)IDesktopContext is the central dependency injection hub. It lives in shared/src/commonMain and is provided to the Compose tree via LocalDesktopContext. Every composable accesses it through DesktopContextScope.withDesktopContext { ... } which gives access to all managers, theme, palette, user, and image loader.
The concrete desktop implementation is DesktopContext in shared/src/desktopMain. The Android implementation is in shared/src/androidMain.
All managers are interface-based (IAppsManager, IAiManager, IPalette, etc.) and lazily instantiated via ManagerCache using Kotlin property delegation (by this on IDesktopContext). Managers live under shared/src/commonMain/.../managers/:
ai/ — AI integration (Gemini, OpenAI plugins, STT/TTS)apps/ — .desktop file parsing and app catalogueconnectivity/ — network statekeys/ — keyboard shortcutspalette/ — dynamic color extraction from wallpaperprocess/ — launching processestheme/ — theme management (GTK theme auto-generation)translations/ — i18nAll shared UI lives in shared/src/commonMain/.../components/. Platform-specific overrides (e.g. window management) are in desktopMain and androidMain. Key components:
desktop/ — the root desktop surfaceappbar/ — top/bottom barsappsmenu/ — application launchercontrolcenter/ — settings panel (pages wired via IDesktopContext.controlCenterPages)background/ — animated wallpaper with crossfade and queuedesktoppanel/ — configurable desktop panelgreeter/ — login/greeter screenwindow/ — window chrome and management (desktop-only)commonMain — shared UI + all interfaces and manager contractsdesktopMain — JVM/AWT specifics: window management, DesktopContext, platform extensionsandroidMain — Android-specific context and extensionsnativeInterop — cinterop definitions for the compositor shimcompositor/)The mjdevc binary is a Kotlin/Native executable. It:
wlroots-0.18 through compositor/native/shim.c (a flat C API compiled to libmjcshim.a)compositor/src/linuxX64Main/) receives view lifecycle callbacks (WindowModel, Policy, GeometryStore), manages IPC to the Kotlin shell (Ipc.kt), and handles session startup (Session.kt)--shell-cmd)Kotlin-first rule: Any desktop behavior (autohide, menus, layout, state, UX) must be implemented in the Kotlin shell. Only add C code for genuine Wayland compositor concerns: seat/keyboard/pointer focus of wlr surfaces, surface layer ordering, output/mode handling.
A behavior must work in plain ./gradlew :desktopApp:run (JVM, no compositor) first.
The Wayland session entry is session/mjdev.desktop (installed to /usr/share/wayland-sessions/). The session launcher is session/mjdev-session (shell script). The compositor runtime requires: libwlroots-0.18 xwayland libegl1 libgles2 libgbm1 libinput10 libseat1 libxkbcommon0 libgl1-mesa-dri dbus dbus-user-session — single source of truth in gradle/libs.versions.toml under app-compositor-runtime-deps.
Session logs go to ~/.cache/mjdev-desktop/session.log.
./gradlew buildAll produces:
releases/mjdev-desktop-<version>.deb — full self-installable deb (includes compositor + session + wayland runtime Depends)releases/mjdev-desktop-<version>.AppImagereleases/mjdev-desktop-<version>.rpmreleases/mjdev-desktop-<version>.apkreleases/mjdev-desktop-<version>.iso (if ISO tools present)Output packages/ directory structure: packages/main-release/{deb,rpm,appimage}/ and packages/main/app/ (distributable).
ktlint_standard_no-unused-imports = disabled (see .editorconfig) because receiver/extension imports are used and would be falsely stripped.gradle/libs.versions.toml. Never hardcode these values in build scripts.enum for grouped constants; use companion object only for single-file constants.Format: <type>(<scope>): <description> — types: feat, fix, docs, style, refactor, test, chore, perf. Description under 50 characters, imperative mood, lowercase.
Branch naming: <type>/<issue-number>-<short-description> — types: feature, bugfix, hotfix, release, support.