From 3104a61fe314f58a8c0cda19cbd4329557358ae8 Mon Sep 17 00:00:00 2001 From: "Elie G." Date: Tue, 1 Sep 2026 03:19:33 +0300 Subject: [PATCH] fix: propagate composable menu DSL state changes to the native menu rememberRecordedMenuContent recorded the user's @Composable menu lambda into a plain mutableListOf. When state read inside the lambda changed, Compose restarted only the lambda's own recompose scope: it re-recorded into the abandoned RecordingComposableScope while the outer function (fingerprint, replay block, the underlying Tray's LaunchedEffect) never re-ran, so label/enabled/checked changes never reached the native menu unless callers disposed the tray via key(). Backing the recorded ops with mutableStateListOf makes the lambda-only restart write snapshot state that the Tray wrapper reads via snapshot(); the wrapper is invalidated, re-records the whole menu with a fresh scope, computes a new fingerprint, and the native menu rebuilds. Verified with a timed repro on Windows: each state flip now produces a menu update with the new label, with no recomposition loop. Fixes #434 --- .../menu/impl/RecordingComposableScope.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/jvmMain/kotlin/dev/nucleusframework/composenativetray/menu/impl/RecordingComposableScope.kt b/src/jvmMain/kotlin/dev/nucleusframework/composenativetray/menu/impl/RecordingComposableScope.kt index 93ca3d3a..e21d5787 100644 --- a/src/jvmMain/kotlin/dev/nucleusframework/composenativetray/menu/impl/RecordingComposableScope.kt +++ b/src/jvmMain/kotlin/dev/nucleusframework/composenativetray/menu/impl/RecordingComposableScope.kt @@ -1,6 +1,7 @@ package dev.nucleusframework.composenativetray.menu.impl import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector @@ -22,7 +23,12 @@ import org.jetbrains.compose.resources.painterResource * functions inside the tray DSL without hoisting them above `application { … }`. */ internal class RecordingComposableScope : ComposableTrayMenuScope { - private val ops = mutableListOf() + // Snapshot-state backed: when only the user's menu lambda recomposes (a state it reads + // changed), the DSL re-records into this list. Readers of snapshot() — the Tray wrapper + // that computes the menu fingerprint — get invalidated by that write and re-record the + // whole menu with a fresh scope, so label/enabled/checked changes reach the native menu + // without the caller disposing the tray (issue #434). + private val ops = mutableStateListOf() fun snapshot(): List = ops.toList()