app

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

commit f0632a1b7479e8cc243af0d96697c83658cce12c
parent e62626f7fbdc10490347e5365e04f733f039ea3d
Author: triesap <tyson@radroots.org>
Date:   Fri, 14 Aug 2026 17:36:09 +0000

ui: protect lifecycle canvas window chrome

- Keep canvas backgrounds full-bleed beneath host-owned chrome
- Move header foreground outside the physical exclusion region
- Expand the top band when host chrome is taller than design metrics
- Verify content bounds and the complete shared desktop suite

Diffstat:
Mapp/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/layout/HarvestCircleCanvasFrame.kt | 21++++++++++++++++++---
Mapp/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/CanvasScaffoldTest.kt | 27+++++++++++++++++++++++++++
Mapp/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/HarvestCircleTestContent.kt | 19+++++++++++++------
3 files changed, 58 insertions(+), 9 deletions(-)

diff --git a/app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/layout/HarvestCircleCanvasFrame.kt b/app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/layout/HarvestCircleCanvasFrame.kt @@ -2,8 +2,10 @@ package org.harvestcircle.designsystem.layout import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.absolutePadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -14,6 +16,8 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.unit.dp import org.harvestcircle.designsystem.shell.HarvestCircleShellMetrics import org.harvestcircle.designsystem.shell.HarvestCircleShellPalette @@ -30,15 +34,26 @@ public fun HarvestCircleCanvasFrame( bodyScrollable: Boolean = false, ) { val colors = HarvestCircleShellPalette - Box(modifier.fillMaxSize().background(colors.viewportCanvas), contentAlignment = Alignment.Center) { + BoxWithConstraints(modifier.fillMaxSize().background(colors.viewportCanvas), contentAlignment = Alignment.Center) { + val chromeClearance = + resolveHarvestCircleWindowChromeClearance( + exclusion = HarvestCircleWindowChrome.exclusion, + windowWidth = maxWidth, + regionLeft = 0.dp, + regionWidth = maxWidth, + minimumTopBandHeight = HarvestCircleShellMetrics.topBarHeight, + ) Column(Modifier.fillMaxSize().background(colors.pane)) { Row( modifier = Modifier .fillMaxWidth() - .height(HarvestCircleShellMetrics.topBarHeight) + .height(chromeClearance.topBandHeight) .background(colors.applicationFrame) - .padding(horizontal = HarvestCircleShellMetrics.contentPageHorizontalInset), + .absolutePadding( + left = maxOf(HarvestCircleShellMetrics.contentPageHorizontalInset, chromeClearance.left), + right = maxOf(HarvestCircleShellMetrics.contentPageHorizontalInset, chromeClearance.right), + ).testTag("harvestcircle-canvas-chrome-content"), verticalAlignment = Alignment.CenterVertically, ) { Box { navigation() } diff --git a/app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/CanvasScaffoldTest.kt b/app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/CanvasScaffoldTest.kt @@ -8,9 +8,13 @@ import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.v2.runComposeUiTest +import androidx.compose.ui.unit.dp import org.harvestcircle.appearance.AppearanceState import org.harvestcircle.appearance.TextSizePreference +import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeEdge +import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeExclusion import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -47,6 +51,29 @@ class CanvasScaffoldTest { } assertTrue(onNodeWithTag("canvas-body").fetchSemanticsNode().config.contains(SemanticsActions.ScrollBy)) } + + @Test + fun macOsChromeExclusionProtectsHeaderForegroundAndMovesBodyBelowTallChrome() = + runComposeUiTest { + setHarvestCircleContent( + windowChromeExclusion = + HarvestCircleWindowChromeExclusion( + edge = HarvestCircleWindowChromeEdge.Left, + width = 112.dp, + height = 56.dp, + ), + ) { + canvas(TextSizePreference.Default) + } + + val chromeContent = onNodeWithTag("harvestcircle-canvas-chrome-content").fetchSemanticsNode().boundsInRoot + val header = onNodeWithTag("canvas-header").fetchSemanticsNode().boundsInRoot + val body = onNodeWithTag("canvas-body").fetchSemanticsNode().boundsInRoot + + assertEquals(112f, chromeContent.left) + assertTrue(header.left >= 112f) + assertTrue(body.top >= 56f) + } } @androidx.compose.runtime.Composable diff --git a/app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/HarvestCircleTestContent.kt b/app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/HarvestCircleTestContent.kt @@ -4,14 +4,21 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.test.ComposeUiTest import androidx.compose.ui.test.ExperimentalTestApi import org.harvestcircle.appearance.AppearanceState +import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeEnvironment +import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeExclusion @OptIn(ExperimentalTestApi::class) -internal fun ComposeUiTest.setHarvestCircleContent(content: @Composable () -> Unit) { +internal fun ComposeUiTest.setHarvestCircleContent( + windowChromeExclusion: HarvestCircleWindowChromeExclusion = HarvestCircleWindowChromeExclusion.None, + content: @Composable () -> Unit, +) { setContent { - HarvestCircleTheme( - appearance = AppearanceState(), - systemDark = false, - content = content, - ) + HarvestCircleWindowChromeEnvironment(windowChromeExclusion) { + HarvestCircleTheme( + appearance = AppearanceState(), + systemDark = false, + content = content, + ) + } } }