mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-09-14 00:55:08 +00:00
refactor: move reply-context logic to commons.model; keep the Compose compiler in commons by measurement
Closes the last two items of documented debt from the commons/commonsUI split. ParentNote (replyingDirectlyTo, isCommunityDefinition) and ReplyContext are pure thread logic used by ViewModels, so they move from the misleading `ui.note` package to `commons.model`, next to ThreadAssembler, together with their tests and the StubCache fixture that shared the package. No `ui.*` package is left in commons. The `ui.note` composables in commonsUI gain explicit imports; consumer imports rewritten. Whether commons still needs the Compose compiler plugin was an open question; it is now measured. With compiler reports on the three GUI modules and full, non-incremental recompiles in both configurations, removing the plugin flips composable parameters typed with unannotated commons classes (TopFilter, TorSettings, ProfileBroadcastStatus, ScheduledPost, EmojiPackState, ...) from runtime-stable to unstable: 20→28 in commonsUI, 33→65 in desktopApp, 90→149 in amethyst. The plugin stays; the numbers are recorded in the build file, ARCHITECTURE.md and the split plan so the question is not reopened. Verified: JVM compiles for commons, commonsUI, cli, desktopApp; Android debug compiles for nappletHost and amethyst; commons/commonsUI/cli/desktopApp JVM test suites. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N56KzPSYiN5edMRamvKEgD
This commit is contained in:
@@ -61,8 +61,10 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.concord.ConcordChannel
|
||||
import com.vitorpamplona.amethyst.commons.model.isCommunityDefinition
|
||||
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel
|
||||
import com.vitorpamplona.amethyst.commons.model.replyingDirectlyTo
|
||||
import com.vitorpamplona.amethyst.commons.model.textNoteModifications
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.approve
|
||||
@@ -78,8 +80,6 @@ import com.vitorpamplona.amethyst.commons.ui.note.QuietMark
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.RenderCashuMint
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.RenderFedimint
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.RenderMintRecommendation
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.isCommunityDefinition
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.replyingDirectlyTo
|
||||
import com.vitorpamplona.amethyst.commons.ui.state.produceCachedStateAsync
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannelPicture
|
||||
|
||||
@@ -35,10 +35,10 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.replyingDirectlyTo
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ReplyToLabel
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.replyingDirectlyTo
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
|
||||
@@ -34,8 +34,8 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.replyingDirectlyTo
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.replyingDirectlyTo
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
|
||||
@@ -75,7 +75,7 @@ in `commonsUI`, under the same package.
|
||||
### Domain models & data
|
||||
| Package | UI? | Purpose |
|
||||
|----------------|-----|---------|
|
||||
| `model` | no¹ | Core domain types (`Note`, `User`, `Channel`), thread assembly, and per-NIP event model extensions in `model/nipNN…` subpackages. `model/cache` holds the in-memory event-store interfaces + `UserMetadataCache`. `model/account`, `model/observables`. The largest package; keep it organized by NIP. |
|
||||
| `model` | no¹ | Core domain types (`Note`, `User`, `Channel`), thread assembly (`ThreadAssembler`, `ThreadLevelCalculator`, `ReplyContext`, `replyingDirectlyTo`), and per-NIP event model extensions in `model/nipNN…` subpackages. `model/cache` holds the in-memory event-store interfaces + `UserMetadataCache`. `model/account`, `model/observables`. The largest package; keep it organized by NIP. |
|
||||
| `defaults` | no | Static bootstrap data (default relays, channels). |
|
||||
|
||||
¹ `model` uses only the `@Stable`/`@Immutable` runtime annotations — CLI-safe.
|
||||
@@ -126,7 +126,7 @@ type (`LazyListState`, `TextFieldValue`, `TextFieldState`) goes to `commonsUI`.
|
||||
### UI (Compose — lives in **`commonsUI`**)
|
||||
| Package | UI? | Purpose |
|
||||
|----------------|-----|---------|
|
||||
| `ui` | yes | **Cross-cutting** shared composables only, organized by area: `ui/components`, `ui/theme`, `ui/signing`, `ui/thread`, `ui/note`, `ui/richtext`, `ui/search`, `ui/notifications`, `ui/screens`, `ui/layouts`, `ui/markdown`, `ui/privacylock`, plus Compose helpers in `ui/state` (cached-state) and `ui/text` (TextField extensions). Feature-specific UI lives in `<feature>/ui`, **not** here. **Exception:** `ui/note/ParentNote`+`ReplyContext` (pure thread logic) stay in *this* module. The `ui/feeds` composables (`NewPostsChip`, `RelayReachMarker`…) are in `commonsUI`; the feed DAL that used to share that package now lives in `feeds/` here. |
|
||||
| `ui` | yes | **Cross-cutting** shared composables only, organized by area: `ui/components`, `ui/theme`, `ui/signing`, `ui/thread`, `ui/note`, `ui/richtext`, `ui/search`, `ui/notifications`, `ui/screens`, `ui/layouts`, `ui/markdown`, `ui/privacylock`, plus Compose helpers in `ui/state` (cached-state) and `ui/text` (TextField extensions). Feature-specific UI lives in `<feature>/ui`, **not** here. Nothing under `ui.*` lives in *this* module any more: the feed DAL that used to sit in `ui/feeds` is now `feeds/`, and the reply-context logic that sat in `ui/note` (`replyingDirectlyTo`, `ReplyContext`) is now in `model/` next to `ThreadAssembler`. |
|
||||
| `nip23LongContent` | yes | Long-form (NIP-23) article UI: `nip23LongContent/ui/article` (reader) + `…/ui/editor` (authoring). The model lives in `model/nip23LongContent` (here). |
|
||||
| `icons` | yes | `ImageVector` icon definitions + builders, Material Symbols codepoints, the icon-font glyph tables. |
|
||||
| `hashtags` | yes | Custom hashtag `ImageVector`s. |
|
||||
@@ -232,9 +232,13 @@ compiles: `commonMain` → `jvmAndroid` → platform-specific. See
|
||||
|
||||
These are intentionally *documented*, not silently tolerated. Fix opportunistically.
|
||||
|
||||
- **`ui/note` in `commons`** still hosts `ParentNote` + `ReplyContext` (pure
|
||||
thread logic used by ViewModels) under a `ui.*` package name. Candidate for
|
||||
`model/` when touched next.
|
||||
- **`commons` applies the Compose *compiler* plugin without declaring any
|
||||
composable.** Deliberate, not debt: the plugin's `@StabilityInferred`
|
||||
stamps are what keep unannotated commons classes stable from the apps'
|
||||
point of view. Measured on full recompiles (2026-09-12): without it,
|
||||
unstable composable params go 20→28 in `commonsUI`, 33→65 in `desktopApp`,
|
||||
90→149 in `amethyst`. Don't remove it; if a class must be stable for a
|
||||
hot path, annotate it explicitly as well.
|
||||
- **Same package tree in two modules.** Intentional (zero-import-churn split),
|
||||
but it means a package's module is not visible from its name. Rule of
|
||||
thumb: if it imports Compose UI it is in `commonsUI`; check §2 when unsure.
|
||||
|
||||
@@ -11,9 +11,13 @@ plugins {
|
||||
alias(libs.plugins.androidKotlinMultiplatformLibrary)
|
||||
// Kept on purpose even though no @Composable lives here anymore: the
|
||||
// Compose compiler stamps @StabilityInferred on every class it compiles,
|
||||
// which is what lets the apps' composables treat commons models (Note,
|
||||
// User, states) as stable/skippable. Dropping it would silently make all
|
||||
// of them "unstable" from the UI's point of view.
|
||||
// which is what lets the apps' composables treat unannotated commons
|
||||
// classes (TopFilter, TorSettings, ProfileBroadcastStatus, …) as stable.
|
||||
// Measured with Compose compiler reports on full recompiles (2026-09-12):
|
||||
// removing this plugin turns 20→28 composable params unstable in
|
||||
// :commonsUI, 33→65 in :desktopApp and 90→149 in :amethyst. Inference is
|
||||
// also self-maintaining, unlike hand-written @Immutable annotations that
|
||||
// silently lie once a `var` is added — so this stays.
|
||||
alias(libs.plugins.jetbrainsComposeCompiler)
|
||||
alias(libs.plugins.serialization)
|
||||
}
|
||||
|
||||
@@ -84,11 +84,29 @@ next to the `:commons` ones on macOS.
|
||||
existing `feeds` package, next to `feeds/custom`). Consumer imports rewritten.
|
||||
- **Chess composables moved to `nip64Chess/ui`** in `commonsUI`; the logic
|
||||
stays in `commons/…/nip64Chess/`.
|
||||
- **`ui/note/ParentNote` + `ReplyContext` moved to `commons/…/model/`** next to
|
||||
`ThreadAssembler` (with the `StubCache` test fixture). No `ui.*` package is
|
||||
left in `commons`.
|
||||
|
||||
## Open
|
||||
## Decided: `commons` keeps the Compose compiler plugin
|
||||
|
||||
Measured rather than guessed. With `composeCompiler { reportsDestination }`
|
||||
on the three GUI modules and full (non-incremental, `--rerun`) compiles in
|
||||
both configurations, removing the plugin from `commons` flips composable
|
||||
parameters typed with unannotated commons classes from runtime-stable to
|
||||
unstable:
|
||||
|
||||
| Module | unstable params, plugin on | plugin off |
|
||||
|---|---|---|
|
||||
| `commonsUI` | 20 | 28 |
|
||||
| `desktopApp` | 33 | 65 |
|
||||
| `amethyst` (fdroidDebug) | 90 | 149 |
|
||||
|
||||
The classes involved (`TopFilter`, `TorSettings`, `TorServiceStatus`,
|
||||
`ProfileBroadcastStatus`, `ScheduledPost`, `EmojiPackState`,
|
||||
`Nip65RelayListState`, `PendingAuthApproval`, `UserSearchEngine`, …) are
|
||||
all-`val` classes with no `@Stable`/`@Immutable` annotation; the plugin infers
|
||||
their stability and keeps inferring it as they evolve. Hand-annotating them
|
||||
would reproduce today's result but rot silently the first time a `var` is
|
||||
added, so the plugin stays. Recorded in `commons/build.gradle.kts`.
|
||||
|
||||
- `commons` still applies the Compose *compiler* plugin on purpose (stability
|
||||
inference for its model classes as seen from the apps' composables).
|
||||
Revisit if a `runtime-annotation`-only setup proves sufficient.
|
||||
- `ui/note/ParentNote` + `ReplyContext` remain in `commons` under a `ui.*`
|
||||
package name (pure logic; candidate for `model/`).
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.ui.note
|
||||
package com.vitorpamplona.amethyst.commons.model
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.ui.note
|
||||
package com.vitorpamplona.amethyst.commons.model
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.ui.note
|
||||
package com.vitorpamplona.amethyst.commons.model
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.ui.note
|
||||
package com.vitorpamplona.amethyst.commons.model
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.ui.note
|
||||
package com.vitorpamplona.amethyst.commons.model
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||
@@ -92,6 +92,7 @@ import com.vitorpamplona.amethyst.commons.feeds.FeedState
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.ReplyContext
|
||||
import com.vitorpamplona.amethyst.commons.model.nip02FollowList.FollowAction
|
||||
import com.vitorpamplona.amethyst.commons.model.nip05DnsIdentifiers.namecoin.NamecoinResolveState
|
||||
import com.vitorpamplona.amethyst.commons.model.nip25Reactions.ReactionAction
|
||||
@@ -110,7 +111,6 @@ import com.vitorpamplona.amethyst.commons.ui.feeds.NewPostsChip
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.StickToTopOnPrepend
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.rememberNewPostsChipState
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.GenericRepostLayout
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ReplyContext
|
||||
import com.vitorpamplona.amethyst.commons.util.toTimeAgo
|
||||
import com.vitorpamplona.amethyst.desktop.DesktopPreferences
|
||||
import com.vitorpamplona.amethyst.desktop.SearchHistoryStore
|
||||
|
||||
+1
-1
@@ -56,12 +56,12 @@ import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.model.ReplyContext
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.commons.richtext.CachedRichTextParser
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||
import com.vitorpamplona.amethyst.commons.richtext.UrlParser
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ReplyContext
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ReplyToLabel
|
||||
import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache
|
||||
import com.vitorpamplona.amethyst.desktop.ui.components.ToggleableTimeAgoText
|
||||
|
||||
Reference in New Issue
Block a user