From d8899eedf8c6e11662d6330f48fb4b21917bb422 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 30 May 2026 19:02:55 +0000 Subject: [PATCH] refactor(commons): move feature-specific UI out of ui/ into /ui Make the feature-UI vs cross-cutting-UI rule consistent (feature-first): - ui/nip53LiveActivities -> nip53LiveActivities/ui - ui/article + ui/editor -> new nip23LongContent/ui (article reader + editor) ui/ now holds only cross-cutting composables (theme, components, layouts, elements, markdown, signing, thread, feeds, notifications, screens, state, text). Tighten ARCHITECTURE.md with the deciding test ('could a second unrelated feature reuse this as-is?') and reconcile the NIP-second-axis section so a single-NIP feature owns its UI under /ui rather than ui/nipNN. https://claude.ai/code/session_01KXLzsvx9Gyrm3Yz4Rims55 --- .../chats/feed/types/RenderChatClip.kt | 2 +- .../chats/feed/types/RenderChatRaid.kt | 2 +- .../chats/feed/types/RenderChatZap.kt | 2 +- commons/ARCHITECTURE.md | 31 +++++++++++++------ .../ui/article/ArticleHeader.kt | 2 +- .../ui/article/TableOfContents.kt | 2 +- .../ui/editor/MarkdownEditorState.kt | 2 +- .../ui/editor/MarkdownToolbar.kt | 2 +- .../ui/editor/MetadataPanel.kt | 2 +- .../ui}/StreamSystemCard.kt | 2 +- .../desktop/ui/ArticleEditorScreen.kt | 6 ++-- .../desktop/ui/ArticleReaderScreen.kt | 6 ++-- 12 files changed, 36 insertions(+), 25 deletions(-) rename commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/{ => nip23LongContent}/ui/article/ArticleHeader.kt (98%) rename commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/{ => nip23LongContent}/ui/article/TableOfContents.kt (98%) rename commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/{ => nip23LongContent}/ui/editor/MarkdownEditorState.kt (99%) rename commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/{ => nip23LongContent}/ui/editor/MarkdownToolbar.kt (98%) rename commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/{ => nip23LongContent}/ui/editor/MetadataPanel.kt (98%) rename commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/{ui/nip53LiveActivities => nip53LiveActivities/ui}/StreamSystemCard.kt (97%) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChatClip.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChatClip.kt index ec48bcc826..f75b7079f6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChatClip.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChatClip.kt @@ -35,7 +35,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.commons.ui.nip53LiveActivities.StreamSystemCard +import com.vitorpamplona.amethyst.commons.nip53LiveActivities.ui.StreamSystemCard import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.playback.composable.VideoView import com.vitorpamplona.amethyst.ui.navigation.navs.INav diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChatRaid.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChatRaid.kt index a055eb4624..1f30363149 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChatRaid.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChatRaid.kt @@ -37,7 +37,7 @@ import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols -import com.vitorpamplona.amethyst.commons.ui.nip53LiveActivities.StreamSystemCard +import com.vitorpamplona.amethyst.commons.nip53LiveActivities.ui.StreamSystemCard import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChatZap.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChatZap.kt index 8291362e06..593ca7a780 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChatZap.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChatZap.kt @@ -36,7 +36,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.commons.ui.nip53LiveActivities.StreamSystemCard +import com.vitorpamplona.amethyst.commons.nip53LiveActivities.ui.StreamSystemCard import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.note.CrossfadeToDisplayComment diff --git a/commons/ARCHITECTURE.md b/commons/ARCHITECTURE.md index 3e99f4410b..31c18cf334 100644 --- a/commons/ARCHITECTURE.md +++ b/commons/ARCHITECTURE.md @@ -78,7 +78,7 @@ package contains Compose UI (and is therefore *not* CLI-safe). | `account` | no | New-account bootstrap events. | | `onchain` | no | On-chain zap splitting/broadcasting. | | `marmot` | no | MLS group-chat event processing. | -| `nip53LiveActivities` | no | Live-activity zapper aggregation. | +| `nip53LiveActivities` | mixed | Live-activity zapper aggregation (logic) + the stream card in `nip53LiveActivities/ui`. | | `search` | no | Event search filtering/ranking, kind registry. | | `preview` | no | OpenGraph / meta-tag link-preview parsing. | | `emojicoder` | no | Variation-selector emoji encode/decode. | @@ -116,7 +116,8 @@ they are shared across the GUI apps. Treat as GUI-shared, not strictly headless. ### UI (Compose — **not** CLI-safe) | Package | UI? | Purpose | |----------------|-----|---------| -| `ui` | yes | All shared composables, organized by area: `ui/components`, `ui/theme`, `ui/signing`, `ui/thread`, `ui/feeds` (feed DAL + filters — see debt §4), `ui/notifications`, `ui/screens`, `ui/article`, `ui/editor`, `ui/elements`, `ui/layouts`, `ui/markdown`, `ui/nip53LiveActivities`, plus Compose helpers in `ui/state` (cached-state) and `ui/text` (TextField extensions). | +| `ui` | yes | **Cross-cutting** shared composables only, organized by area: `ui/components`, `ui/theme`, `ui/signing`, `ui/thread`, `ui/feeds` (feed DAL + filters — see debt §4), `ui/notifications`, `ui/screens`, `ui/elements`, `ui/layouts`, `ui/markdown`, plus Compose helpers in `ui/state` (cached-state) and `ui/text` (TextField extensions). Feature-specific UI lives in `/ui`, **not** here. | +| `nip23LongContent` | yes | Long-form (NIP-23) article UI: `nip23LongContent/ui/article` (reader) + `…/ui/editor` (authoring). The model lives in `model/nip23LongContent`. | | `icons` | yes | `ImageVector` icon definitions + builders. | | `hashtags` | yes | Custom hashtag `ImageVector`s. | | `robohash` | yes | Procedural robohash avatar `ImageVector` assembly. | @@ -146,8 +147,14 @@ share them. ### Naming - **Singular, no synonyms.** `util` (not `utils`), `service` (not `services`). One concept → one package name. -- Composables that belong to a feature go in `/ui`; cross-cutting - composables go under `ui/`. +- **Feature UI vs cross-cutting UI — the deciding test.** A composable goes in + `/ui` if it renders/edits *one* feature's content (it would make no + sense outside that feature) — e.g. `profile/ui`, `nip53LiveActivities/ui`, + `nip23LongContent/ui`. It goes in `ui/` only if it is reusable across + features (theme, avatars, buttons, layouts, markdown rendering, shimmer…). + When in doubt, ask "could a second, unrelated feature reuse this as-is?" — + yes → `ui/`, no → `/ui`. The top-level `ui/` package holds + **no** feature-specific composables. ### NIP as the second axis (mirror `quartz`) `quartz` is ~94% organized by NIP (`nipNN` per spec), and that is correct @@ -160,16 +167,20 @@ problem one level down. Instead, **layer is the primary axis, NIP is the secondary axis**: -- The big shared layers stay layer-organized (`model`, `ui`, `relayClient`, …). +- The big shared layers stay layer-organized (`model`, `relayClient`, the + cross-cutting `ui`, …). - **Inside a layer, NIP-specific code goes in a `nipNN` subpackage whose - name matches `quartz` exactly** — e.g. `model/nip57Zaps`, `ui/nip53LiveActivities`. - This gives a clean trace: `quartz/nip57Zaps` → `commons/model/nip57Zaps` → - `commons/ui/nip57Zaps`. + name matches `quartz` exactly** — e.g. `model/nip57Zaps`. This gives a clean + trace: `quartz/nip57Zaps` → `commons/model/nip57Zaps`. - **A top-level package that *is* a single self-contained NIP feature takes the - same name as its `quartz` counterpart**: `nip64Chess`, `nipACWebRtcCalls`, - `nip53LiveActivities`, `marmot`. (`marmot` is un-numbered in `quartz` too.) + same name as its `quartz` counterpart**, and owns its own UI under + `/ui`: `nip64Chess`, `nipACWebRtcCalls`, `nip53LiveActivities`, + `nip23LongContent`, `marmot`. (`marmot` is un-numbered in `quartz` too.) Generic/multi-NIP packages keep their concern name (`search`, `preview`, `actions`, `richtext`…). +- **Feature UI is never under `ui/`.** A single-NIP feature's composables live + in `/ui` (e.g. `nip53LiveActivities/ui`), not `ui/nip53LiveActivities`. + `ui/` is exclusively cross-cutting (§2, Naming). ### Source sets | Source set | For | diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/article/ArticleHeader.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/article/ArticleHeader.kt similarity index 98% rename from commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/article/ArticleHeader.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/article/ArticleHeader.kt index 92ca76cd25..0a2fc0e117 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/article/ArticleHeader.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/article/ArticleHeader.kt @@ -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.article +package com.vitorpamplona.amethyst.commons.nip23LongContent.ui.article import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/article/TableOfContents.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/article/TableOfContents.kt similarity index 98% rename from commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/article/TableOfContents.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/article/TableOfContents.kt index 16d2985acb..57ef6abba1 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/article/TableOfContents.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/article/TableOfContents.kt @@ -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.article +package com.vitorpamplona.amethyst.commons.nip23LongContent.ui.article import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/editor/MarkdownEditorState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/editor/MarkdownEditorState.kt similarity index 99% rename from commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/editor/MarkdownEditorState.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/editor/MarkdownEditorState.kt index 64a0f5e281..438c1ec919 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/editor/MarkdownEditorState.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/editor/MarkdownEditorState.kt @@ -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.editor +package com.vitorpamplona.amethyst.commons.nip23LongContent.ui.editor import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/editor/MarkdownToolbar.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/editor/MarkdownToolbar.kt similarity index 98% rename from commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/editor/MarkdownToolbar.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/editor/MarkdownToolbar.kt index 1338886645..67b5fd7745 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/editor/MarkdownToolbar.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/editor/MarkdownToolbar.kt @@ -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.editor +package com.vitorpamplona.amethyst.commons.nip23LongContent.ui.editor import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/editor/MetadataPanel.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/editor/MetadataPanel.kt similarity index 98% rename from commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/editor/MetadataPanel.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/editor/MetadataPanel.kt index 49ee321ea0..e6285eecde 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/editor/MetadataPanel.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip23LongContent/ui/editor/MetadataPanel.kt @@ -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.editor +package com.vitorpamplona.amethyst.commons.nip23LongContent.ui.editor import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/nip53LiveActivities/StreamSystemCard.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip53LiveActivities/ui/StreamSystemCard.kt similarity index 97% rename from commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/nip53LiveActivities/StreamSystemCard.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip53LiveActivities/ui/StreamSystemCard.kt index 2b888da72a..93586401a0 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/nip53LiveActivities/StreamSystemCard.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip53LiveActivities/ui/StreamSystemCard.kt @@ -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.nip53LiveActivities +package com.vitorpamplona.amethyst.commons.nip53LiveActivities.ui import androidx.compose.foundation.background import androidx.compose.foundation.clickable diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleEditorScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleEditorScreen.kt index 48a9035e2c..519d3260cd 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleEditorScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleEditorScreen.kt @@ -65,9 +65,9 @@ import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.nip23LongContent.LongFormPublishAction -import com.vitorpamplona.amethyst.commons.ui.editor.MarkdownEditorState -import com.vitorpamplona.amethyst.commons.ui.editor.MarkdownToolbar -import com.vitorpamplona.amethyst.commons.ui.editor.MetadataPanel +import com.vitorpamplona.amethyst.commons.nip23LongContent.ui.editor.MarkdownEditorState +import com.vitorpamplona.amethyst.commons.nip23LongContent.ui.editor.MarkdownToolbar +import com.vitorpamplona.amethyst.commons.nip23LongContent.ui.editor.MetadataPanel import com.vitorpamplona.amethyst.commons.ui.markdown.RenderMarkdown import com.vitorpamplona.amethyst.desktop.account.AccountState import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleReaderScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleReaderScreen.kt index 2113ae1cba..9f5c7a42aa 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleReaderScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleReaderScreen.kt @@ -69,9 +69,9 @@ import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.nip23LongContent.ReadingTimeCalculator -import com.vitorpamplona.amethyst.commons.ui.article.ArticleHeader -import com.vitorpamplona.amethyst.commons.ui.article.TableOfContents -import com.vitorpamplona.amethyst.commons.ui.article.extractTableOfContents +import com.vitorpamplona.amethyst.commons.nip23LongContent.ui.article.ArticleHeader +import com.vitorpamplona.amethyst.commons.nip23LongContent.ui.article.TableOfContents +import com.vitorpamplona.amethyst.commons.nip23LongContent.ui.article.extractTableOfContents import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.components.LoadingState import com.vitorpamplona.amethyst.commons.ui.markdown.RenderMarkdown