Android now ships on laptops, so the whole UI (screens and the navigation shell) moves to commonsUI, amethyst becomes an Android shim, and a new JVM desktopApp replaces the current one with the same UI. - commons/plans/2026-09-27-one-ui-android-desktop.md: the decision, what it supersedes in the sweep tracker, the prerequisites (androidx navigation-compose publishes only jvmStubs for JVM; the Amethyst.instance app root is read by 178 files; Android-only libraries inside screens), and Wave 4 measured: Account's move-group is 77 files / ~23.9k lines inside model/, with 5 hard-blocked files and 14 exit edges, each with a proposed cut; AccountViewModel's Android imports and 29 outside dependencies. - CLAUDE.md, commons/ARCHITECTURE.md, commonsUI/ARCHITECTURE.md and the kotlin-multiplatform, compose-expert and desktop-expert skills no longer say screens and navigation stay platform-native. - The sweep tracker marks its STAY list, Wave 2 part B and the Desktop-phase merges as superseded, and re-scopes Wave 4 from decomposition to moving Account and AccountViewModel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P
4.4 KiB
commonsUI — the Compose half of the shared layer
commonsUI holds every piece of shared code that needs Compose UI and is
therefore useless to the headless cli:
- composables (
ui/<area>,<feature>/ui— e.g.nip64Chess/ui,profile/ui— and the historical flataudiorenderers), ImageVectoricons (icons,hashtags,robohash) and the icon fonts,- theme, layouts, markdown rendering (
ui/markdown, jvmAndroid), - Coil (
service/image:CoilImageBridge+ BlurHash/ThumbHash/Base64/Blossom fetchers), - the
composeResourcestree (translated strings, fonts, the napplet shell + shim files) and the generatedcom.vitorpamplona.amethyst.commons.resources.Res, - the
@Composablerelay-client entry points (observeUser*,*FilterAssemblerSubscription,KeyDataSourceSubscription), - state holders that carry a
foundation/uitype (LevelFeedViewModelwith itsLazyListState,ChatNewMessageStatewithTextFieldValue,EmojiSuggestionStatewithTextFieldState).
Its end state is the whole app UI: every screen, the navigation host and the
navigation chrome for every window size (bottom bar, rail, permanent drawer), with
amethyst and a new JVM desktopApp as thin shims around it. Screens still in
amethyst/ are waiting on AccountViewModel and the app root, not staying there
by design. See commons/plans/2026-09-27-one-ui-android-desktop.md.
It depends on :commons (and :quartz) as api, so a consumer that adds
:commonsUI sees the headless layer transitively. amethyst, desktopApp,
nappletHost and benchmark depend on it; cli, geode, marmotBench
must never.
Same packages as commons
Every file keeps its com.vitorpamplona.amethyst.commons.* package. The split
is a build-graph boundary, not a package rename: nothing in the apps had to
change an import, and a file can move between the two modules with a git mv.
The rule for which module a file lives in is mechanical:
imports
androidx.compose.ui/foundation/material3/animation, Coil,org.jetbrains.compose.resources,Res,org.jetbrains.skia, or declares a@Composable→commonsUI. Otherwise →commons.
The only cross-module gotcha is internal: a commonsUI file cannot see an
internal declaration in commons. Make it public (or move the caller).
The full package taxonomy — including which packages are "mixed" (logic in
commons, composables here, same package name) — is the table in
commons/ARCHITECTURE.md §2. Read that first; this file only documents what
is specific to the UI module.
Source sets
| Source set | For |
|---|---|
commonMain |
Composables, icons, theme, Coil fetchers, composeResources. Gated by verifyKmpPurity like commons. |
jvmAndroid |
Markdown renderer (ui/markdown), Coil-OkHttp + Blossom read-auth fetcher, the viewModel() helper, and the avatar/image engine (ui/components/RobohashAsyncImage.kt: RobohashFallbackAsyncImage, GifProfilePicture) behind the UserAvatar AvatarImage actual. For a user avatar call the commonMain UserAvatar; don't add another avatar composable. |
jvmMain |
Desktop Coil bridge (CoilImageBridge.jvm.kt), a no-op AnimatedImageAutoPlay (Coil decodes only the first frame), compose.desktop.currentOs. dependsOn(jvmAndroid) + skikoMain. |
androidMain |
Android Coil bridge, the AnimatedImageAutoPlay actual that starts/stops GIF/AVIF drawables. dependsOn(jvmAndroid). |
skikoMain |
org.jetbrains.skia pixel helpers shared by desktop JVM + iOS (SkiaBitmapConverter). |
iosMain |
iOS Coil bridge, a plain AsyncImage AvatarImage actual. Compile-only spike today. |
Tooling that points here
- Icon fonts:
tools/material-symbols-subset/subset.shandtools/icon-font/build_icon_font.pyreadicons/and writecomposeResources/font/in this module (see root.claude/CLAUDE.md, "Icons"). - Translations:
crowdin.ymlandtools/strings-migrate/targetcommonsUI/src/commonMain/composeResources/. This is where every new user-visible string goes, Android-only screens included; the app's ownres/values/strings.xmlholds only the synchronous-platform tier (see root.claude/CLAUDE.md, "Strings"). - CI:
.github/workflows/build.ymlruns:commonsUI:jvmTest,:commonsUI:verifyKmpPurityand the iOS compile/test tasks next to the:commonsones.