app

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

commit e62626f7fbdc10490347e5365e04f733f039ea3d
parent 3a205f0693374301a6950a50d853acdeaf1c9e7f
Author: triesap <tyson@radroots.org>
Date:   Fri, 14 Aug 2026 17:33:51 +0000

desktop: provide macOS window chrome geometry

- Install fail-fast host chrome state per application window
- Reserve a conservative physical macOS traffic-light region
- Keep non-macOS windows free of host chrome exclusions
- Verify desktop chrome policy and environment integration

Diffstat:
Mapp/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/layout/HarvestCircleWindowChrome.kt | 29+++++++++++++++++++++++++++++
Mapp/desktop/src/main/kotlin/org/harvestcircle/desktop/Main.kt | 32+++++++++++++++++++++++++-------
Mapp/desktop/src/test/kotlin/org/harvestcircle/desktop/MainTest.kt | 16++++++++++++++++
3 files changed, 70 insertions(+), 7 deletions(-)

diff --git a/app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/layout/HarvestCircleWindowChrome.kt b/app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/layout/HarvestCircleWindowChrome.kt @@ -1,6 +1,10 @@ package org.harvestcircle.designsystem.layout +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.isSpecified @@ -45,6 +49,31 @@ public data class HarvestCircleWindowChromeClearance( public val right: Dp, ) +private val LocalHarvestCircleWindowChromeExclusion = + staticCompositionLocalOf<HarvestCircleWindowChromeExclusion> { + error("HarvestCircleWindowChromeEnvironment is missing from the composition") + } + +/** Read-only access to the host-owned exclusion for the current window. */ +public object HarvestCircleWindowChrome { + public val exclusion: HarvestCircleWindowChromeExclusion + @Composable + @ReadOnlyComposable + get() = LocalHarvestCircleWindowChromeExclusion.current +} + +/** Installs immutable host chrome geometry for exactly one application window. */ +@Composable +public fun HarvestCircleWindowChromeEnvironment( + exclusion: HarvestCircleWindowChromeExclusion, + content: @Composable () -> Unit, +) { + CompositionLocalProvider( + LocalHarvestCircleWindowChromeExclusion provides exclusion, + content = content, + ) +} + /** * Resolves the portion of [exclusion] intersecting a top-edge region. * diff --git a/app/desktop/src/main/kotlin/org/harvestcircle/desktop/Main.kt b/app/desktop/src/main/kotlin/org/harvestcircle/desktop/Main.kt @@ -16,6 +16,9 @@ import org.harvestcircle.application.HarvestCircleApplication import org.harvestcircle.application.NativeHarvestCircleRuntime import org.harvestcircle.application.desktopRuntimeOpenConfiguration import org.harvestcircle.application.verifyNativeCompatibility +import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeEdge +import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeEnvironment +import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeExclusion import org.harvestcircle.ffi.HarvestCircleException import org.harvestcircle.ffi.compatibilityDescriptor import org.harvestcircle.ui.shell.StartupFailureScreen @@ -42,6 +45,8 @@ internal const val INITIAL_WINDOW_WIDTH = 1280 internal const val INITIAL_WINDOW_HEIGHT = 800 internal const val MINIMUM_WINDOW_WIDTH = 1100 internal const val MINIMUM_WINDOW_HEIGHT = 720 +internal const val MACOS_WINDOW_CHROME_EXCLUSION_WIDTH = 112 +internal const val MACOS_WINDOW_CHROME_EXCLUSION_HEIGHT = 40 internal const val HEALTH_CHECK_ARGUMENT = "--health-check" internal const val HEALTH_READY_EVIDENCE = "HARVESTCIRCLE_HEALTH_READY" internal const val HEALTH_CLOSED_EVIDENCE = "HARVESTCIRCLE_HEALTH_CLOSED" @@ -85,13 +90,15 @@ fun main(args: Array<String>) { onDispose { } } - if (nativeStartupProblem == null) { - HarvestCircleApplication( - closeRequested = closeRequested, - onExitApproved = ::exitApplication, - ) - } else { - StartupFailureScreen(nativeStartupProblem) + HarvestCircleWindowChromeEnvironment(desktopWindowChromeExclusion(isMacOs)) { + if (nativeStartupProblem == null) { + HarvestCircleApplication( + closeRequested = closeRequested, + onExitApproved = ::exitApplication, + ) + } else { + StartupFailureScreen(nativeStartupProblem) + } } } } @@ -99,6 +106,17 @@ fun main(args: Array<String>) { internal fun desktopWindowTitle(macOs: Boolean): String = if (macOs) "" else APPLICATION_NAME +internal fun desktopWindowChromeExclusion(macOs: Boolean): HarvestCircleWindowChromeExclusion = + if (macOs) { + HarvestCircleWindowChromeExclusion( + edge = HarvestCircleWindowChromeEdge.Left, + width = MACOS_WINDOW_CHROME_EXCLUSION_WIDTH.dp, + height = MACOS_WINDOW_CHROME_EXCLUSION_HEIGHT.dp, + ) + } else { + HarvestCircleWindowChromeExclusion.None + } + internal fun configureMacOsWindowChrome( rootPane: JRootPane, windowAccessibility: AccessibleContext, diff --git a/app/desktop/src/test/kotlin/org/harvestcircle/desktop/MainTest.kt b/app/desktop/src/test/kotlin/org/harvestcircle/desktop/MainTest.kt @@ -1,8 +1,11 @@ package org.harvestcircle.desktop +import androidx.compose.ui.unit.dp import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import org.harvestcircle.application.ApplicationLifecycle +import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeEdge +import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeExclusion import java.io.ByteArrayInputStream import java.nio.file.Files import javax.swing.JRootPane @@ -37,6 +40,19 @@ class MainTest { } @Test + fun macOsWindowChromeReservesAConservativePhysicalExclusion() { + assertEquals( + HarvestCircleWindowChromeExclusion( + edge = HarvestCircleWindowChromeEdge.Left, + width = 112.dp, + height = 40.dp, + ), + desktopWindowChromeExclusion(macOs = true), + ) + assertEquals(HarvestCircleWindowChromeExclusion.None, desktopWindowChromeExclusion(macOs = false)) + } + + @Test fun missingOrInvalidRuntimeIconFailsSafely() { assertNull(loadRuntimeIcon { null }) assertNull(loadRuntimeIcon { ByteArrayInputStream("not an image".encodeToByteArray()) })