`:commons` is on the CLI classpath, yet it declared Compose UI, Coil, Compose resources, markdown and desktop Compose as dependencies, dragging ~40 MB of UI/Skiko jars into every `amy` distribution. This moves every Compose-dependent file into a new KMP module, `:commonsUI`, that `api`-depends on `:commons`; `:commons` keeps only the Compose runtime (stability annotations + snapshot state) and lifecycle-viewmodel. Files keep their `com.vitorpamplona.amethyst.commons.*` packages, so the split is a build-graph boundary and no consumer import changed. 236 files were `git mv`'d (composables, icons, robohash, theme, Coil fetchers, the `@Composable` relay-client entry points, `composeResources`, and the tests that exercise them). Two headless files needed surgery instead of a move: `GalleryParser` lost a vestigial foundation `@OptIn`, and the `LocalPrivacyLockState`/`lockStateFor` CompositionLocal accessor moved out of `PrivacyLockState` into its own commonsUI file. The feed DAL under `ui/feeds` and `ui/note/ParentNote`+`ReplyContext` stay in `commons` because ViewModels depend on them. `amethyst`, `desktopApp`, `nappletHost` (NappletWebContract serves the shell from composeResources) and `benchmark` now depend on `:commonsUI`; `cli`, `geode` and `marmotBench` do not. commons' androidMain gains an explicit androidx.core KTX dep it previously got transitively through Compose UI. CI, crowdin, the icon-font tools and the escaping hook point at the new composeResources location; CLAUDE.md, commons/ARCHITECTURE.md, a new commonsUI/ARCHITECTURE.md, CONTRIBUTING, BUILDING and the affected skills document the boundary. A plan doc under commons/plans records the classification method and follow-ups. Verified: JVM compiles for commons, commonsUI, cli, desktopApp; Android debug compiles for nappletHost and amethyst; commons/commonsUI/cli JVM test suites; both verifyKmpPurity gates; the cli runtime classpath no longer resolves Compose UI, material3, Skiko or Coil. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N56KzPSYiN5edMRamvKEgD
3.3 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, and the historical flat feature packages likenip64Chess,audio), 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).
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. |
jvmMain |
Desktop Coil bridge (CoilImageBridge.jvm.kt), compose.desktop.currentOs. dependsOn(jvmAndroid) + skikoMain. |
androidMain |
Android Coil bridge. dependsOn(jvmAndroid). |
skikoMain |
org.jetbrains.skia pixel helpers shared by desktop JVM + iOS (SkiaBitmapConverter). |
iosMain |
iOS Coil bridge. 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/. - CI:
.github/workflows/build.ymlruns:commonsUI:jvmTest,:commonsUI:verifyKmpPurityand the iOS compile/test tasks next to the:commonsones.