The 2026-09-22 move left Android res/ with only the ~190 strings a platform
API reads synchronously. The cordn UI and the backup-conflict review then
added 447 keys back to it. All of them move with tools/strings-migrate
(1,131 elements across the locales that translate them), and the 18 files
that read them now use Res.string/Res.plurals.
The parts that were not a rename:
- stringRes(context, ...) in the suspend attachment/voice senders becomes
loadStringRes; adminFailureText becomes suspend (its only caller is already
inside scope.launch).
- The backup review held labels as @StringRes Int (DiffGroup.label, the
ReviewRow *Res fields, eventTypeName/Explainer, the tile/hero/lane params).
They are StringResource / PluralStringResource now.
- rememberPresentation resolved labels inside remember {} through
LocalResources. Compose resources have no blocking accessor, so the 16
labels presentationOf writes into item text are resolved in composition
into a map, and that map is the remember key.
- androidx pluralStringResource -> the compose-resources one.
The rule is now in .claude/CLAUDE.md ("Strings"), commonsUI/ARCHITECTURE.md
and the android-expert and find-missing-translations skills: new strings go
in commonsUI composeResources, Android-only screens included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjQQN9CgWHVtNCSWqnKoqY
4.0 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).
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.