app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 1b17496d1b4e802183bd2f639c0fa087137e8ade
parent a4fab8f9067c2e46a77386ac0cd82014404f5543
Author: triesap <tyson@radroots.org>
Date:   Tue, 11 Aug 2026 16:54:06 +0000

ui: implement the foundation Today screen

- Render the locked empty Today state from actual shell session context.
- Keep circle exploration visibly disabled with its deferred explanation.
- Route Nostr reference entry through the existing typed overlay contract.
- Verify component, application, formatting, Rust, and desktop test suites.

Diffstat:
Mapp/desktop/src/test/kotlin/org/harvestcircle/application/HarvestCircleApplicationTest.kt | 6+++---
Aapp/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationTodayScreen.kt | 47+++++++++++++++++++++++++++++++++++++++++++++++
Mapp/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/HarvestCircleShell.kt | 25++++++++++++++++++++++++-
Aapp/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/FoundationTodayScreenTest.kt | 38++++++++++++++++++++++++++++++++++++++
4 files changed, 112 insertions(+), 4 deletions(-)

diff --git a/app/desktop/src/test/kotlin/org/harvestcircle/application/HarvestCircleApplicationTest.kt b/app/desktop/src/test/kotlin/org/harvestcircle/application/HarvestCircleApplicationTest.kt @@ -10,7 +10,6 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithTag @@ -81,8 +80,9 @@ class HarvestCircleApplicationTest { onNodeWithText("Coordinate local food with clear, signed terms.").assertIsDisplayed() onAllNodesWithText("Connect a remote signer").assertCountEquals(0) onNodeWithTag("bootstrap-read-only").performClick() - waitUntil { onAllNodesWithTag("foundation-route-body").fetchSemanticsNodes().isNotEmpty() } - onNodeWithTag("foundation-route-body").assertIsDisplayed().assertTextEquals("Today") + waitUntil { onAllNodesWithTag("foundation-today").fetchSemanticsNodes().isNotEmpty() } + onNodeWithTag("foundation-today").assertIsDisplayed() + onNodeWithText("Read-only session").assertIsDisplayed() } @OptIn(ExperimentalTestApi::class) diff --git a/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationTodayScreen.kt b/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationTodayScreen.kt @@ -0,0 +1,47 @@ +package org.harvestcircle.ui.shell + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.text.BasicText +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.unit.dp + +data class FoundationTodayModel( + val context: String, +) + +@Composable +fun FoundationTodayScreen( + model: FoundationTodayModel, + openNostrReference: () -> Unit, +) { + SingleFocusTemplate { + Column( + Modifier.testTag("foundation-today"), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + BasicText(model.context, Modifier.testTag("today-context")) + BasicText("No active commitments") + BasicText("Explore nearby buying circles or open a shared Nostr reference.") + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + ShellAction( + "Explore circles", + "Explore circles", + "today-explore-circles", + enabled = false, + onClick = {}, + ) + ShellAction( + "Open a Nostr reference", + "Open a Nostr reference", + "today-open-reference", + onClick = openNostrReference, + ) + } + BasicText("Not available in this build.", Modifier.testTag("today-deferred-helper")) + } + } +} diff --git a/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/HarvestCircleShell.kt b/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/HarvestCircleShell.kt @@ -148,10 +148,33 @@ private fun DashboardRoot( }, sidebar = { WorkspaceSidebar(destination) { dispatch(HarvestCircleShellIntent.Navigate(it)) } }, mainHeader = { MainPanelHeader(MainPanelHeaderModel(title = route.title())) }, - mainBody = { BasicText(route.title(), Modifier.testTag("foundation-route-body")) }, + mainBody = { + when (route) { + AppRoute.PersonalToday -> + FoundationTodayScreen( + model = FoundationTodayModel(todayContext(state)), + openNostrReference = { + dispatch( + HarvestCircleShellIntent.Overlay( + OverlayIntent.Open(FoundationOverlay.OpenNostrReference()), + ), + ) + }, + ) + else -> BasicText(route.title(), Modifier.testTag("foundation-route-body")) + } + }, ) } +private fun todayContext(state: HarvestCircleShellState): String = + when { + state.session.readOnly -> "Read-only session" + state.identity.snapshot.activeIdentity != null -> + state.identity.snapshot.activeIdentity.identity.displayLabel + else -> "Signed out" + } + private fun dispatchTopBar( intent: GlobalTopBarIntent, dispatch: (HarvestCircleShellIntent) -> Unit, diff --git a/app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/FoundationTodayScreenTest.kt b/app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/FoundationTodayScreenTest.kt @@ -0,0 +1,38 @@ +package org.harvestcircle.ui.shell + +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.assertTextEquals +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runComposeUiTest +import kotlin.test.Test +import kotlin.test.assertEquals + +@OptIn(ExperimentalTestApi::class) +class FoundationTodayScreenTest { + @Test + fun readOnlyEmptyStateIsTruthfulAndReferenceActionIsTyped() = + runComposeUiTest { + var opened = 0 + setContent { + FoundationTodayScreen(FoundationTodayModel("Read-only session")) { opened += 1 } + } + + onNodeWithText("Read-only session").assertExists() + onNodeWithText("No active commitments").assertExists() + onNodeWithText("Explore nearby buying circles or open a shared Nostr reference.").assertExists() + onNodeWithTag("today-explore-circles").assertIsNotEnabled() + onNodeWithText("Not available in this build.").assertExists() + onNodeWithTag("today-open-reference").performClick() + assertEquals(1, opened) + } + + @Test + fun activeContextUsesTheActualIdentityLabel() = + runComposeUiTest { + setContent { FoundationTodayScreen(FoundationTodayModel("Local grower"), openNostrReference = {}) } + onNodeWithTag("today-context").assertTextEquals("Local grower") + } +}