mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 11:18:24 +00:00
`: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
138 lines
5.6 KiB
Markdown
138 lines
5.6 KiB
Markdown
# Extract-from-Android recipe
|
|
|
|
The single most common reason an Amy feature request stalls: the
|
|
logic it needs lives in `amethyst/` with Android-only imports. You
|
|
cannot call it from `cli/`. You have to move it first.
|
|
|
|
This recipe is how.
|
|
|
|
## When to extract
|
|
|
|
Before writing a new command, ask:
|
|
|
|
1. Does the piece of logic I need exist in `quartz/` or `commons/`?
|
|
- **Yes** → use it.
|
|
- **No, but it's in `amethyst/`** → extract. This file.
|
|
- **No, it doesn't exist anywhere** → design it in `commons/`
|
|
directly. Write a plan doc under `commons/plans/` if it's a
|
|
new subsystem.
|
|
|
|
2. Never duplicate `amethyst/` logic into `cli/`. That's a debt you
|
|
will pay later when the Android caller drifts.
|
|
|
|
## Recipe
|
|
|
|
Land this as its own commit, **before** the commit that adds the
|
|
CLI command.
|
|
|
|
### Step 1 — Find the class
|
|
|
|
```bash
|
|
grep -rn "fun followUser\|class FollowListManager" amethyst/src/main/java/
|
|
```
|
|
|
|
Identify the minimum unit to move. Sometimes it's a whole file,
|
|
sometimes one function. Prefer the smallest unit that makes the
|
|
command possible.
|
|
|
|
### Step 2 — List Android-only dependencies
|
|
|
|
Walk the imports. The usual offenders:
|
|
|
|
| Dependency | Treatment |
|
|
|---|---|
|
|
| `android.content.Context` | Often accidental — inline if only used for logging or preferences. Otherwise, invert as constructor arg. |
|
|
| `android.content.SharedPreferences` | Abstract behind an interface in `commons/`; Android actual uses SharedPreferences, JVM actual uses a JSON file. |
|
|
| `androidx.work.WorkManager` | Rarely shareable — if the CLI needs it, simplify the flow to not require background scheduling. |
|
|
| `android.util.Log` | Replace with `quartz` `PlatformLog` (already multiplatform). |
|
|
| `android.graphics.Bitmap` | Almost never needed by Amy. Keep in Android and split the function. |
|
|
| `android.net.Uri` | Replace with `kotlinx.io` path types or a plain `String`. |
|
|
| `androidx.compose.*` | Compose UI (`ui`/`foundation`/`material3`), Coil and `Res` must stay out of `commons` entirely — they belong in `:commonsUI`, which Amy never depends on. Only the Compose *runtime* (`@Stable`, snapshot state) is allowed in `commons`. |
|
|
|
|
### Step 3 — Pick a migration strategy per dependency
|
|
|
|
- **Inline-able.** One call, trivial. Delete it.
|
|
- **Platform-abstractable.** Add `expect` in `commons/commonMain/` +
|
|
`actual` in `commons/androidMain/` + `actual` in `commons/jvmMain/`.
|
|
See `kotlin-multiplatform` skill for the mechanics and for the
|
|
`jvmAndroid` source-set pattern used throughout this repo.
|
|
- **Inversion-of-control.** Take the Android dependency as a
|
|
constructor arg with an interface type. Amy supplies a JVM flavour;
|
|
Android supplies the Context-backed one.
|
|
|
|
### Step 4 — Move the code
|
|
|
|
```bash
|
|
# Target location depends on what it is:
|
|
# - Protocol → quartz/src/commonMain/kotlin/…
|
|
# - Business logic → commons/src/commonMain/kotlin/…
|
|
# - UI → commonsUI/src/commonMain/… (needs Compose Multiplatform; never used by amy)
|
|
git mv amethyst/src/main/java/com/.../FollowListManager.kt \
|
|
commons/src/commonMain/kotlin/com/.../FollowListManager.kt
|
|
```
|
|
|
|
Update package declarations. Run `./gradlew spotlessApply`.
|
|
|
|
### Step 5 — Update the Android caller
|
|
|
|
The amethyst/ caller now imports from the new location. Often this
|
|
is the only code change visible in the Android app.
|
|
|
|
If the Android caller was using a concrete Android-backed
|
|
dependency, it now supplies that concrete dependency explicitly.
|
|
|
|
### Step 6 — Add a JVM test
|
|
|
|
In `commons/src/commonTest/kotlin/…` or `commons/src/jvmTest/kotlin/…`
|
|
(depending on what the code exercises), add a test that runs on JVM.
|
|
This guards against Android-only imports sneaking back in, and it's
|
|
the only way to be sure Amy can now call the code.
|
|
|
|
### Step 7 — Commit
|
|
|
|
Single commit, descriptive:
|
|
|
|
> refactor(follow): extract FollowListManager to commons for CLI reuse
|
|
>
|
|
> Move FollowListManager from amethyst/model/nip02FollowLists/ to
|
|
> commons/commonMain/.../followLists/. Android's SharedPreferences
|
|
> dependency is inverted behind FollowListStore (interface); Android
|
|
> keeps the SharedPreferences-backed actual, new JvmFollowListStore
|
|
> writes JSON to disk. No behaviour change on Android.
|
|
|
|
### Step 8 — Now add the CLI command
|
|
|
|
Separate commit. Follows the pattern in `command-template.md`.
|
|
|
|
## Cautionary notes
|
|
|
|
- **Don't extract speculatively.** Only extract what the current
|
|
command needs. A feature-complete port can happen later; right now
|
|
the goal is to unblock one command without adding surface area you
|
|
don't have a second caller for.
|
|
- **Android is allowed to keep side-effects.** Notifications,
|
|
background services, Intents, camera, permissions dialogs — those
|
|
stay in `amethyst/`. Amy's job isn't to replicate UX, it's to
|
|
exercise the protocol underneath.
|
|
- **Check the consumers.** Sometimes the "logic" you want is already
|
|
partially in `commons/`, and the `amethyst/` class is just a thin
|
|
wrapper. In that case, re-use the `commons/` class directly and
|
|
delete the wrapper or keep it if Android genuinely needs it.
|
|
- **Tests first if you're nervous.** Copy the existing
|
|
`amethyst/`-side test (if any), make it JVM-only by removing
|
|
Android imports, and watch it pass after the move.
|
|
|
|
## Red flags during extraction
|
|
|
|
Stop and reconsider if:
|
|
|
|
- The Android class is 1000+ lines. Extract only the piece the CLI
|
|
needs; leave the rest for a follow-up.
|
|
- You need `Context` in 30 places. It's probably being used as a
|
|
grab-bag; sort by actual use (strings, preferences, services, …)
|
|
and abstract those individually.
|
|
- You find yourself writing `expect class` with a dozen methods. A
|
|
fine-grained interface is usually clearer than a monolithic
|
|
expect-actual.
|
|
- You're about to add a Compose import to `cli/`. Stop.
|