From 3a46d396f66d724f6adca1f4edb280d4695d88b1 Mon Sep 17 00:00:00 2001 From: veryCrunchy Date: Sun, 6 Sep 2026 06:07:06 +0200 Subject: [PATCH 01/13] docs(changelog): record media route ownership --- changes/unreleased/media-route-account-ownership.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changes/unreleased/media-route-account-ownership.md diff --git a/changes/unreleased/media-route-account-ownership.md b/changes/unreleased/media-route-account-ownership.md new file mode 100644 index 000000000..d61daa1fe --- /dev/null +++ b/changes/unreleased/media-route-account-ownership.md @@ -0,0 +1,7 @@ +category: fix +issue: 172 +pull: none +platforms: android, desktop +user-facing: yes + +Media viewer routes now stay bound to the account and account incarnation that created them, preventing stale media from opening after an account is removed or replaced. From 70a4ae3fc384a010eb31183272f3ffb96e910610 Mon Sep 17 00:00:00 2001 From: veryCrunchy Date: Mon, 14 Sep 2026 00:07:07 +0200 Subject: [PATCH 02/13] docs(security): scope media route guarantees to account removal --- changes/unreleased/media-route-account-ownership.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changes/unreleased/media-route-account-ownership.md b/changes/unreleased/media-route-account-ownership.md index d61daa1fe..637b53e3b 100644 --- a/changes/unreleased/media-route-account-ownership.md +++ b/changes/unreleased/media-route-account-ownership.md @@ -1,7 +1,7 @@ -category: fix +category: security issue: 172 pull: none platforms: android, desktop user-facing: yes -Media viewer routes now stay bound to the account and account incarnation that created them, preventing stale media from opening after an account is removed or replaced. +Media viewer routes are scoped to their owning account and cleared when that account is removed, preventing retained media from reopening through another account or after removal. From 3ac8406038dad5f612af72cf99d8c89c5912900a Mon Sep 17 00:00:00 2001 From: veryCrunchy Date: Mon, 14 Sep 2026 02:54:39 +0200 Subject: [PATCH 03/13] fix(media): keep full-screen workspace state registered once --- .../unreleased/465-media-workspace-state.md | 7 ++ tools/kotlin-file-size-baseline.txt | 2 +- .../nextcloudnative/app/AppWorkspaceState.kt | 14 ++++ .../nextcloudnative/app/NextcloudNativeApp.kt | 77 ++++++++----------- .../app/AppWorkspaceStateTest.kt | 67 ++++++++++++++++ 5 files changed, 122 insertions(+), 45 deletions(-) create mode 100644 changes/unreleased/465-media-workspace-state.md create mode 100644 ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt create mode 100644 ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceStateTest.kt diff --git a/changes/unreleased/465-media-workspace-state.md b/changes/unreleased/465-media-workspace-state.md new file mode 100644 index 000000000..ca5f87911 --- /dev/null +++ b/changes/unreleased/465-media-workspace-state.md @@ -0,0 +1,7 @@ +category: fix +issue: 465 +pull: none +platforms: android, desktop +user-facing: yes + +Keep app workspace state registered once when entering or leaving full-screen media, preventing image navigation from crashing during the layout change. diff --git a/tools/kotlin-file-size-baseline.txt b/tools/kotlin-file-size-baseline.txt index 108a1c5d0..81f48667c 100644 --- a/tools/kotlin-file-size-baseline.txt +++ b/tools/kotlin-file-size-baseline.txt @@ -25,7 +25,7 @@ ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NativeDeckBoardSurface. ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NativeDeckRelationDialogs.kt|1234 ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NativeDeckScreen.kt|1892 ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudMediaViewer.kt|1331 -ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt|12432 +ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt|12417 ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNotes.kt|1693 ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudPhotoEditor.kt|808 ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudPlatform.kt|1724 diff --git a/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt b/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt new file mode 100644 index 000000000..b1d2a5fc8 --- /dev/null +++ b/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt @@ -0,0 +1,14 @@ +package dev.obiente.nextcloudnative.app + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.saveable.SaveableStateHolder + +/** Keeps registration at one composition location when the navigation shell changes. */ +@Composable +internal fun AppWorkspaceState( + holder: SaveableStateHolder, + appId: String?, + content: @Composable () -> Unit, +) { + if (appId == null) content() else holder.SaveableStateProvider("app:$appId", content) +} diff --git a/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt b/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt index 31c7202dc..10bf1d91c 100644 --- a/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt +++ b/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt @@ -2608,52 +2608,41 @@ private fun AuthenticatedApp( }, ) } - Box(modifier = Modifier.weight(1f).fillMaxWidth()) { - if (shouldUseNextcloudRootShell(presentation, screen.usesPersistentAppNavigation())) { - RootShell( - presentation = presentation, - selected = destination, - desktopWorkspaceKind = if (screen == Screen.Root) { - NextcloudDesktopWorkspaceKind.Root - } else { - NextcloudDesktopWorkspaceKind.AppWorkspace - }, - navigationEnabled = !groupwareMutationInProgress, - onSelected = { selected -> - inlineEditorNavigation.navigate { - if (!groupwareMutationInProgress) { - leaveAppWorkspace() - destination = selected - screen = Screen.Root - } - } - }, - identity = desktopIdentity, - activeAppId = appWorkspaceNavigation.activeAppId, - onOpenApp = { appId -> - if (!groupwareMutationInProgress) { - serverInfo?.apps?.firstOrNull { it.id == appId }?.let { app -> - openApp(app, destination) + AppWorkspaceState( + holder = appWorkspaceSaveableStateHolder, + appId = appWorkspaceNavigation.activeAppId?.takeIf { screen != Screen.Root }, + ) { + Box(modifier = Modifier.weight(1f).fillMaxWidth()) { + if (shouldUseNextcloudRootShell(presentation, screen.usesPersistentAppNavigation())) { + RootShell( + presentation = presentation, + selected = destination, + desktopWorkspaceKind = if (screen == Screen.Root) { + NextcloudDesktopWorkspaceKind.Root + } else { + NextcloudDesktopWorkspaceKind.AppWorkspace + }, + navigationEnabled = !groupwareMutationInProgress, + onSelected = { selected -> + inlineEditorNavigation.navigate { + if (!groupwareMutationInProgress) { + leaveAppWorkspace() + destination = selected + screen = Screen.Root + } } - } - }, - content = { - val appId = appWorkspaceNavigation.activeAppId - if (appId != null && screen != Screen.Root) { - appWorkspaceSaveableStateHolder.SaveableStateProvider("app:$appId") { - screenContent() + }, + identity = desktopIdentity, + activeAppId = appWorkspaceNavigation.activeAppId, + onOpenApp = { appId -> + if (!groupwareMutationInProgress) { + serverInfo?.apps?.firstOrNull { it.id == appId }?.let { app -> + openApp(app, destination) + } } - } else { - screenContent() - } - }, - ) - } else { - val appId = appWorkspaceNavigation.activeAppId - if (appId != null && screen != Screen.Root) { - appWorkspaceSaveableStateHolder.SaveableStateProvider("app:$appId") { - screenContent() - } + }, + content = screenContent, + ) } else { screenContent() } diff --git a/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceStateTest.kt b/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceStateTest.kt new file mode 100644 index 000000000..2ba234029 --- /dev/null +++ b/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceStateTest.kt @@ -0,0 +1,67 @@ +package dev.obiente.nextcloudnative.app + +import androidx.compose.runtime.AbstractApplier +import androidx.compose.runtime.BroadcastFrameClock +import androidx.compose.runtime.Composition +import androidx.compose.runtime.Recomposer +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.saveable.rememberSaveableStateHolder +import androidx.compose.runtime.snapshots.Snapshot +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield +import kotlin.test.Test +import kotlin.test.assertEquals + +class AppWorkspaceStateTest { + @Test fun shellChangesDoNotRegisterAnAppKeyTwiceAndAppStateRestores() = runBlocking { + val clock = BroadcastFrameClock() + val recomposer = Recomposer(coroutineContext + clock) + val runner = launch(clock) { recomposer.runRecomposeAndApplyChanges() } + val composition = Composition(EmptyApplier(), recomposer) + val fullScreen = mutableStateOf(false) + val app = mutableStateOf("photos") + var observed = -1 + var changeValue: (Int) -> Unit = {} + composition.setContent { + AppWorkspaceState(rememberSaveableStateHolder(), app.value) { + val count = rememberSaveable { mutableStateOf(0) } + SideEffect { observed = count.value; changeValue = { count.value = it } } + // Both presentation branches may be replaced in a single apply pass. + if (fullScreen.value) SideEffect {} else SideEffect {} + } + } + suspend fun settle() { + Snapshot.sendApplyNotifications() + yield() + clock.sendFrame(System.nanoTime()) + recomposer.awaitIdle() + } + try { + settle() + changeValue(7) + settle() + repeat(8) { fullScreen.value = !fullScreen.value; settle(); assertEquals(7, observed) } + app.value = "files" + settle() + assertEquals(0, observed) + app.value = "photos" + settle() + assertEquals(7, observed) + } finally { + composition.dispose() + recomposer.close() + runner.join() + } + } + + private class EmptyApplier : AbstractApplier(Unit) { + override fun insertBottomUp(index: Int, instance: Unit) = Unit + override fun insertTopDown(index: Int, instance: Unit) = Unit + override fun move(from: Int, to: Int, count: Int) = Unit + override fun remove(index: Int, count: Int) = Unit + override fun onClear() = Unit + } +} From c48998a8f5d6ab6d40d008d7d41cee79531efbff Mon Sep 17 00:00:00 2001 From: veryCrunchy Date: Mon, 14 Sep 2026 02:54:51 +0200 Subject: [PATCH 04/13] fix(desktop): restore Intel macOS SQLite runtime --- .github/workflows/ci.yml | 47 +++++++++++++++++++ .github/workflows/nightly.yml | 8 ++++ .github/workflows/prerelease.yml | 8 ++++ PLATFORMS.md | 11 +++++ changes/unreleased/459-intel-macos-sqlite.md | 7 +++ gradle/libs.versions.toml | 3 +- .../app/DesktopSqliteRuntime.kt | 13 +++++ .../nextcloudnative/nativeui/preview/Main.kt | 8 +++- .../app/DesktopSqliteRuntimeTest.kt | 22 +++++++++ 9 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 changes/unreleased/459-intel-macos-sqlite.md create mode 100644 ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntime.kt create mode 100644 ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntimeTest.kt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 521e31eaa..71687a106 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -394,3 +394,50 @@ jobs: steps.changes.outputs.windows != 'true' shell: pwsh run: '"No Windows desktop or build-system changes; native Windows validation was skipped." >> $env:GITHUB_STEP_SUMMARY' + + macos-sqlite-runtime: + name: Intel macOS SQLite runtime + runs-on: macos-15-intel + timeout-minutes: 45 + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Detect SQLite packaging changes + id: changes + continue-on-error: true + uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 + with: + filters: | + sqlite: + - ".github/workflows/ci.yml" + - "gradle/**" + - "gradle.properties" + - "build.gradle.kts" + - "settings.gradle.kts" + - "ui/build.gradle.kts" + - "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntime.kt" + - "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt" + - "ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntimeTest.kt" + + - name: Set up JDK 21 + if: >- + github.event_name == 'workflow_dispatch' || + steps.changes.outcome != 'success' || + steps.changes.outputs.sqlite == 'true' + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: "21" + cache: gradle + + - name: Verify Intel macOS SQLite in tests and the packaged app + if: >- + github.event_name == 'workflow_dispatch' || + steps.changes.outcome != 'success' || + steps.changes.outputs.sqlite == 'true' + run: | + ./gradlew --no-daemon :ui:desktopTest --tests '*DesktopSqliteRuntimeTest' :ui:createDistributable + launcher=$(find ui/build/compose/binaries/main/app -type f -path '*/Contents/MacOS/nati.ve' -print -quit) + test -n "$launcher" + "$launcher" --verify-sqlite-runtime diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1838204cb..36c4605ac 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -307,6 +307,14 @@ jobs: fi exit "${status}" + - name: Verify packaged macOS SQLite runtime + if: matrix.platform == 'macos' + shell: bash + run: | + launcher=$(find ui/build/compose/binaries/main/app -type f -path '*/Contents/MacOS/nati.ve' -print -quit) + test -n "$launcher" + "$launcher" --verify-sqlite-runtime + - name: Verify unsigned Windows MSI if: matrix.platform == 'windows' shell: pwsh diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 3fbd453ee..ee2aacd32 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -203,6 +203,14 @@ jobs: fi exit "${status}" + - name: Verify packaged macOS SQLite runtime + if: matrix.platform == 'macos' + shell: bash + run: | + launcher=$(find ui/build/compose/binaries/main/app -type f -path '*/Contents/MacOS/nati.ve' -print -quit) + test -n "$launcher" + "$launcher" --verify-sqlite-runtime + - name: Verify unsigned Windows MSI if: matrix.platform == 'windows' shell: pwsh diff --git a/PLATFORMS.md b/PLATFORMS.md index ae738df62..d64e1ead3 100644 --- a/PLATFORMS.md +++ b/PLATFORMS.md @@ -191,3 +191,14 @@ bundles; the DMG does not migrate an existing bundle automatically. These names describe source packaging configuration, not confirmation that a release containing it has been published. Check the [release artifacts](https://github.com/Obiente/native/releases) for availability. + +## Desktop SQLite runtime verification + +The JVM SQLite dependency is constrained to 2.6.2 because the 2.7.0 and 2.7.1 +artifacts omit the Intel macOS native library. `DesktopSqliteRuntimeTest` checks +the native resources for each packaged desktop architecture and opens an +in-memory database on the test host. An Intel macOS CI job validates relevant +dependency and packaging changes. Nightly and prerelease macOS packages run their +launcher with `--verify-sqlite-runtime`, before creating application services. +This check does not inspect accounts or modify user databases. A resource check +on one OS does not establish runtime validation on another OS. diff --git a/changes/unreleased/459-intel-macos-sqlite.md b/changes/unreleased/459-intel-macos-sqlite.md new file mode 100644 index 000000000..5941dba1b --- /dev/null +++ b/changes/unreleased/459-intel-macos-sqlite.md @@ -0,0 +1,7 @@ +category: fix +issue: 459 +pull: none +platforms: desktop +user-facing: yes + +Restore the bundled SQLite library for Intel Macs so folder-sync and media-backup databases can open, and verify native runtime resources before packaging. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 92d9c336b..15bfe5733 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,8 @@ media3 = "1.10.1" libvlc = "3.7.5" javafx = "21.0.12" jse = "1.1.0" -sqlite = "2.7.0" +# SQLite 2.7 JVM artifacts omit the Intel macOS native library. +sqlite = { strictly = "2.6.2" } [libraries] kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } diff --git a/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntime.kt b/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntime.kt new file mode 100644 index 000000000..e7f014a92 --- /dev/null +++ b/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntime.kt @@ -0,0 +1,13 @@ +package dev.obiente.nextcloudnative.app + +import androidx.sqlite.driver.bundled.BundledSQLiteDriver + +/** Runs before application services and only uses an in-memory database. */ +internal fun verifyDesktopSqliteRuntime() { + BundledSQLiteDriver().open(":memory:").use { connection -> + connection.prepare("SELECT sqlite_version()").use { statement -> + check(statement.step()) { "SQLite runtime verification returned no version." } + check(statement.getText(0).isNotBlank()) { "SQLite runtime verification returned an empty version." } + } + } +} diff --git a/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt b/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt index 27993892b..692661658 100644 --- a/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt +++ b/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt @@ -58,7 +58,13 @@ import javax.swing.SwingUtilities import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch -fun main(arguments: Array) = runDesktopEntryPoint(arguments) +fun main(arguments: Array) { + if (arguments.contentEquals(arrayOf("--verify-sqlite-runtime"))) { + dev.obiente.nextcloudnative.app.verifyDesktopSqliteRuntime() + return + } + runDesktopEntryPoint(arguments) +} @OptIn(ExperimentalComposeUiApi::class) internal fun runDesktopEntryPoint( diff --git a/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntimeTest.kt b/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntimeTest.kt new file mode 100644 index 000000000..b82d05acd --- /dev/null +++ b/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntimeTest.kt @@ -0,0 +1,22 @@ +package dev.obiente.nextcloudnative.app + +import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import kotlin.test.Test +import kotlin.test.assertNotNull + +class DesktopSqliteRuntimeTest { + @Test fun distributedDriverIncludesEverySupportedDesktopBinary() { + val loader = BundledSQLiteDriver::class.java.classLoader + listOf( + "linux_x64/libsqliteJni.so", + "linux_arm64/libsqliteJni.so", + "osx_x64/libsqliteJni.dylib", + "osx_arm64/libsqliteJni.dylib", + "windows_x64/sqliteJni.dll", + ).forEach { binary -> + assertNotNull(loader.getResource("natives/$binary"), "Missing SQLite runtime: $binary") + } + } + + @Test fun nativeDriverCanOpenAndQueryTheRuntime() = verifyDesktopSqliteRuntime() +} From 1caa8059fc317290d6855c55b098c51e9cf8ba85 Mon Sep 17 00:00:00 2001 From: veryCrunchy Date: Mon, 14 Sep 2026 02:59:33 +0200 Subject: [PATCH 05/13] test(ci): include SQLite scope detection in workflow contract --- tools/test-marketing-capture-workflow.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/test-marketing-capture-workflow.sh b/tools/test-marketing-capture-workflow.sh index 4764eb827..0cc95bf4b 100755 --- a/tools/test-marketing-capture-workflow.sh +++ b/tools/test-marketing-capture-workflow.sh @@ -67,10 +67,10 @@ for stale_gate in \ fi done -scope_detector_count="$(grep -Ec '^ - name: Detect (build scopes|Windows desktop changes)$' "$ci")" +scope_detector_count="$(grep -Ec '^ - name: Detect (build scopes|Windows desktop changes|SQLite packaging changes)$' "$ci")" continued_detector_count="$(grep -Fc 'continue-on-error: true' "$ci")" -if [[ "$scope_detector_count" -ne 2 || "$continued_detector_count" -ne 2 ]]; then - printf 'Build scope detection must degrade safely for both build jobs.\n' >&2 +if [[ "$scope_detector_count" -ne 3 || "$continued_detector_count" -ne 3 ]]; then + printf 'Build scope detection must degrade safely for all three build jobs.\n' >&2 exit 1 fi require_text "$ci" "steps.changes.outcome != 'success'" From 88da746a5f0b4f6083c316bfc2af6f401b1d3f73 Mon Sep 17 00:00:00 2001 From: "obiente-automations[bot]" <311907242+obiente-automations[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 01:02:14 +0000 Subject: [PATCH 06/13] chore(website): refresh marketing captures --- website/public/screenshots/capture-manifest.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/public/screenshots/capture-manifest.json b/website/public/screenshots/capture-manifest.json index 669833d92..4443b5c9f 100644 --- a/website/public/screenshots/capture-manifest.json +++ b/website/public/screenshots/capture-manifest.json @@ -25,6 +25,7 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceNavigationMemory.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePins.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePresentation.kt", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppsWorkspace.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDateTimeFields.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDialogSurface.kt", @@ -440,6 +441,7 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceNavigationMemory.kt": "2a48ec4fda7ca47657253891e880a4169b16dbc83c197808532a92169961d569", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePins.kt": "91842afe8be45598eca9f23da7012401c53c6571737d1a9b38e82332ce84123c", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePresentation.kt": "e5655f80ca80cdb88ac49a54554e7d4cf684293efb2af4e291f0ef4b34529594", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt": "aa277dd2b3da172ad4121470fb0fd5d3d2f4f52e3a2fdadbeac64713cebed800", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppsWorkspace.kt": "87b5ee4b871c85d55a8f43f3cb07ee619672def16fade42f819d67c913ac5295", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDateTimeFields.kt": "dbd41b992d9ebf24f76e6e66b03c8de23cf76267eca831eee318e5a582231d81", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDialogSurface.kt": "c571d170ab27cbe0bf02ecc4ae81b641b89fa6410244a7ce79edc46cc49101e1", @@ -626,7 +628,7 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudLinkRouting.kt": "5b29a90b69bb32aba118ef6c8b3f9d6eb26c03835823119b4a0f5bb1c1f4cb17", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudMediaViewer.kt": "fb424bf8979ac292ef30daba64e905e6f5123066cda793aa0244907e11b465c9", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudMediaViewerActions.kt": "48aaed6948d1423113d300cc3ab86d244ab76e8225e24988e9855edf74553944", - "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt": "923cae9286489364664dfbf28bdf7e7c12ce1cc8e3a7d1a1a51a0397950cbcd2", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt": "5fa9d4de759c046f412d8d05e19e3e27aec92c66d4e6f62e74bf9c4152943b8a", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNotes.kt": "b53e2505663f0957dea9ad231b006d03d08f0405dc459974d85eb4768ef03484", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNotesCache.kt": "ac2206703b224364c1a3ff4097026c9c81d856042c20d31e5c28358016c3062d", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudPeople.kt": "cff910ea2cc77211ef81779c49ee0c957851f2b4a3ed32b857b12ded1cee643b", From 36aa2604b3891dfd63c49f480eda33881719835e Mon Sep 17 00:00:00 2001 From: "obiente-automations[bot]" <311907242+obiente-automations[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 01:06:27 +0000 Subject: [PATCH 07/13] chore(website): refresh marketing captures --- website/public/screenshots/capture-manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/public/screenshots/capture-manifest.json b/website/public/screenshots/capture-manifest.json index 669833d92..4eeffebc5 100644 --- a/website/public/screenshots/capture-manifest.json +++ b/website/public/screenshots/capture-manifest.json @@ -422,7 +422,7 @@ "captureSourceHashes": { "build.gradle.kts": "de2d98f7fce24b2bc67423bb06aa88d251e1458b1fa3c9c9700dbb14289edbf0", "gradle.properties": "99c3ee1a9d68a2548830e7dca0cc4b02f4acf408bfc394a0acd55e7865c53739", - "gradle/libs.versions.toml": "10ef19d481e525d0ad1e94c9a74abe0a4caca6572cf7d2d0dadabfba3974ab5a", + "gradle/libs.versions.toml": "69ed47ea88842a0097cc7dc5273016d8954e668763618976a9b781efe2915dda", "gradle/wrapper/gradle-wrapper.properties": "aef287d114ce3153c3d535697a61928f5034990d570cbb2e93df549e9671483d", "settings.gradle.kts": "0acbe4b907815189abfedb2256c8659558e5a7e6995a3681a2bdfb05e335fd1a", "tools/marketing-capture-inputs.txt": "3c96e83e1ba2d715b1cda9cedf036fc97b78c3ca63b7fc930325ed536940c1f3", @@ -813,7 +813,7 @@ "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/DynamicBoardInteractionPreviewMain.kt": "7402f5b4bf3bbf3cf20761eaa10eca9131fb6e63da2988d0aa6038942d706c53", "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/FileSyncTrayVisualQaMain.kt": "4ce39ce3a53b72a32ca7489ef65cf301e9d8279f4be9ef9afc3ace701075c301", "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/FileSyncWorkspaceVisualQaMain.kt": "af79af05554e41806121b49f7fe8e31a67127636f0faec78f44ac812726c86e0", - "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt": "37681a229096d46fd1eb06a8f969c22a2903e765b6b4eefb887be7b8dea461ad", + "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt": "b6f26394074b06186397d381dc7b20dc4690c36fd37865df5134377188520936", "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/MarketingCaptureMain.kt": "bd7f6b8870c0030f061c5e466849b597f3388df6e9b4c5a6987000bb0dc0fbdf", "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/MarketingCaptureOwnership.kt": "c5bd9b63793a8884cf6b74cadf3061dc0ac295a982c97ae7f0a906ac3ae6f3c9", "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/NativeTiffMarketingCapture.kt": "6970352c6e42d09b793c55ea296991d756d232f216d8d152aa00319b2ddb8d20", From 6156925224101d950a2010d1e41aab054c9bc244 Mon Sep 17 00:00:00 2001 From: veryCrunchy Date: Mon, 14 Sep 2026 03:28:27 +0200 Subject: [PATCH 08/13] docs(release): link Intel Mac runtime fix to its pull request --- changes/unreleased/459-intel-macos-sqlite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/unreleased/459-intel-macos-sqlite.md b/changes/unreleased/459-intel-macos-sqlite.md index 5941dba1b..b4543f218 100644 --- a/changes/unreleased/459-intel-macos-sqlite.md +++ b/changes/unreleased/459-intel-macos-sqlite.md @@ -1,6 +1,6 @@ category: fix issue: 459 -pull: none +pull: 469 platforms: desktop user-facing: yes From cd0ac40149fd0b3be2e96a9a0ea32f2fa84ba9d4 Mon Sep 17 00:00:00 2001 From: "obiente-automations[bot]" <311907242+obiente-automations[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 01:57:45 +0000 Subject: [PATCH 09/13] chore(website): refresh marketing captures --- website/public/screenshots/capture-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/public/screenshots/capture-manifest.json b/website/public/screenshots/capture-manifest.json index 2d6f1cc49..050b1eeec 100644 --- a/website/public/screenshots/capture-manifest.json +++ b/website/public/screenshots/capture-manifest.json @@ -656,7 +656,7 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudResponseLimits.kt": "fead6cbf4f723ea46f99c9155b190967205d24f198687b7bfd5a213c474374b8", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudSession.kt": "792a381fd5eefc44e13eb73ee95a80f8d52e8dcec9d3876ba06ec5392b5c1f81", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudSessionLoading.kt": "12339db06d73f7bb689de6cbf4a4a2ec8e1ffe8c6f10ca627390829b3907a716", - "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudStatusMessages.kt": "22883b8d93d9b1efa389f4857fe5a1e20140fb4a6914d2b93ac94a59d4470d93", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudStatusMessages.kt": "9236bf9c8f959d794e5756ac260232e24a6fb4e9372bb04cc6d2028d71f1ec05", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NotesApi.kt": "e3c685b02592ea791b0c9a478a098fc5bf428893aa4764110c793a78040f341e", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NotesFolderOperations.kt": "db6a04cfcd3b25b17c996cdc6dba21847d14f5970e06e6c24a3e2c85139dbe1d", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/OfficeDocumentWorkflow.kt": "2ddc486b9a948ed1ff5de48d64471df54d55f2bdb055e2446da79b5fb656cdf3", From 25018c4270f4bf6487059b8bca09f630baecef96 Mon Sep 17 00:00:00 2001 From: veryCrunchy Date: Mon, 14 Sep 2026 04:26:34 +0200 Subject: [PATCH 10/13] fix(media): preserve workspace state without retaining detail drafts --- .../unreleased/465-media-workspace-state.md | 4 +- .../nextcloudnative/app/AppWorkspaceState.kt | 48 ++++++- .../nextcloudnative/app/NextcloudNativeApp.kt | 68 +++++----- .../app/AppWorkspaceStateTest.kt | 128 +++++++++++------- 4 files changed, 159 insertions(+), 89 deletions(-) diff --git a/changes/unreleased/465-media-workspace-state.md b/changes/unreleased/465-media-workspace-state.md index ca5f87911..6aab910b9 100644 --- a/changes/unreleased/465-media-workspace-state.md +++ b/changes/unreleased/465-media-workspace-state.md @@ -1,7 +1,7 @@ category: fix issue: 465 -pull: none +pull: 468 platforms: android, desktop user-facing: yes -Keep app workspace state registered once when entering or leaving full-screen media, preventing image navigation from crashing during the layout change. +Keep navigation chrome independent from app state and restore workspace settings when entering or leaving full-screen media, preventing image navigation crashes. Do not retain detail or editor state for restoration into another record. diff --git a/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt b/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt index b1d2a5fc8..25bb08717 100644 --- a/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt +++ b/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt @@ -1,14 +1,54 @@ package dev.obiente.nextcloudnative.app import androidx.compose.runtime.Composable +import androidx.compose.runtime.movableContentOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.SaveableStateHolder -/** Keeps registration at one composition location when the navigation shell changes. */ +/** Moves only workspace content; application chrome keeps its independent state. */ @Composable -internal fun AppWorkspaceState( +internal fun rememberAppWorkspaceContent( holder: SaveableStateHolder, appId: String?, + screen: Screen, content: @Composable () -> Unit, -) { - if (appId == null) content() else holder.SaveableStateProvider("app:$appId", content) +): @Composable () -> Unit { + val routeKind = screen.workspaceStateKind() + // Retain workspace roots only. Detail/editor bundles must not be restored into another record. + val stateKey = appId?.takeIf { screen.usesPersistentAppNavigation() }?.let { "app:$it:$routeKind" } + val movable = remember(holder, appId, routeKind) { + movableContentOf<@Composable () -> Unit> { render -> + if (stateKey == null) render() else holder.SaveableStateProvider(stateKey) { render() } + } + } + return { movable(content) } +} + +/** Keys contain route kinds, never private paths, selected records, or editor content. */ +private fun Screen.workspaceStateKind(): String = when (this) { + Screen.Root -> "root" + Screen.Search -> "search" + is Screen.Files -> "files" + Screen.Media -> "media" + is Screen.PersonMedia -> "person-media" + Screen.Talk -> "talk" + Screen.Notes -> "notes" + Screen.Dashboard -> "dashboard" + Screen.UserStatus -> "user-status" + Screen.Calendar -> "calendar" + Screen.Contacts -> "contacts" + Screen.Tasks -> "tasks" + Screen.Deck -> "deck" + Screen.AdminApps -> "admin-apps" + Screen.OfflineCenter -> "offline-center" + Screen.Transfers -> "transfers" + Screen.ProjectNews -> "project-news" + is Screen.ProjectNewsArticleView -> "project-news-article" + is Screen.Chat -> "chat" + is Screen.NoteEditor -> "note-editor" + is Screen.AppInfo -> "app-info" + is Screen.MediaViewer -> "media-viewer" + is Screen.FileInfo -> "file-info" + is Screen.DocumentPreview -> "document-preview" + is Screen.TextEditor -> "text-editor" } diff --git a/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt b/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt index 10bf1d91c..17bc1dfa8 100644 --- a/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt +++ b/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt @@ -2608,44 +2608,44 @@ private fun AuthenticatedApp( }, ) } - AppWorkspaceState( + val workspaceContent = rememberAppWorkspaceContent( holder = appWorkspaceSaveableStateHolder, appId = appWorkspaceNavigation.activeAppId?.takeIf { screen != Screen.Root }, - ) { - Box(modifier = Modifier.weight(1f).fillMaxWidth()) { - if (shouldUseNextcloudRootShell(presentation, screen.usesPersistentAppNavigation())) { - RootShell( - presentation = presentation, - selected = destination, - desktopWorkspaceKind = if (screen == Screen.Root) { - NextcloudDesktopWorkspaceKind.Root - } else { - NextcloudDesktopWorkspaceKind.AppWorkspace - }, - navigationEnabled = !groupwareMutationInProgress, - onSelected = { selected -> - inlineEditorNavigation.navigate { - if (!groupwareMutationInProgress) { - leaveAppWorkspace() - destination = selected - screen = Screen.Root - } - } - }, - identity = desktopIdentity, - activeAppId = appWorkspaceNavigation.activeAppId, - onOpenApp = { appId -> + screen = screen, content = screenContent, + ) + Box(modifier = Modifier.weight(1f).fillMaxWidth()) { + if (shouldUseNextcloudRootShell(presentation, screen.usesPersistentAppNavigation())) { + RootShell( + presentation = presentation, + selected = destination, + desktopWorkspaceKind = if (screen == Screen.Root) { + NextcloudDesktopWorkspaceKind.Root + } else { + NextcloudDesktopWorkspaceKind.AppWorkspace + }, + navigationEnabled = !groupwareMutationInProgress, + onSelected = { selected -> + inlineEditorNavigation.navigate { if (!groupwareMutationInProgress) { - serverInfo?.apps?.firstOrNull { it.id == appId }?.let { app -> - openApp(app, destination) - } + leaveAppWorkspace() + destination = selected + screen = Screen.Root } - }, - content = screenContent, - ) - } else { - screenContent() - } + } + }, + identity = desktopIdentity, + activeAppId = appWorkspaceNavigation.activeAppId, + onOpenApp = { appId -> + if (!groupwareMutationInProgress) { + serverInfo?.apps?.firstOrNull { it.id == appId }?.let { app -> + openApp(app, destination) + } + } + }, + content = workspaceContent, + ) + } else { + workspaceContent() } } } diff --git a/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceStateTest.kt b/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceStateTest.kt index 2ba234029..e0f7d4f46 100644 --- a/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceStateTest.kt +++ b/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceStateTest.kt @@ -1,67 +1,97 @@ package dev.obiente.nextcloudnative.app -import androidx.compose.runtime.AbstractApplier -import androidx.compose.runtime.BroadcastFrameClock -import androidx.compose.runtime.Composition -import androidx.compose.runtime.Recomposer -import androidx.compose.runtime.SideEffect +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.semantics.getOrNull +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveableStateHolder -import androidx.compose.runtime.snapshots.Snapshot -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.yield +import androidx.compose.runtime.setValue +import dev.obiente.nextcloudnative.app.design.NextcloudDesktopWorkspaceKind +import dev.obiente.nextcloudnative.app.design.NextcloudDestination +import dev.obiente.nextcloudnative.app.design.NextcloudPresentation import kotlin.test.Test -import kotlin.test.assertEquals +import kotlin.test.assertTrue class AppWorkspaceStateTest { - @Test fun shellChangesDoNotRegisterAnAppKeyTwiceAndAppStateRestores() = runBlocking { - val clock = BroadcastFrameClock() - val recomposer = Recomposer(coroutineContext + clock) - val runner = launch(clock) { recomposer.runRecomposeAndApplyChanges() } - val composition = Composition(EmptyApplier(), recomposer) - val fullScreen = mutableStateOf(false) - val app = mutableStateOf("photos") - var observed = -1 - var changeValue: (Int) -> Unit = {} - composition.setContent { - AppWorkspaceState(rememberSaveableStateHolder(), app.value) { - val count = rememberSaveable { mutableStateOf(0) } - SideEffect { observed = count.value; changeValue = { count.value = it } } - // Both presentation branches may be replaced in a single apply pass. - if (fullScreen.value) SideEffect {} else SideEffect {} + @Test fun desktopAppSwitchingPreservesGlobalSidebarAndPerAppContent() { + val app = mutableStateOf("files") + nativeSceneTest(1440, 900, content = { + val workspace = rememberAppWorkspaceContent(rememberSaveableStateHolder(), app.value, Screen.Files("/")) { + SavedCounter("Files") } - } - suspend fun settle() { - Snapshot.sendApplyNotifications() - yield() - clock.sendFrame(System.nanoTime()) - recomposer.awaitIdle() - } - try { - settle() - changeValue(7) + RootShell( + presentation = NextcloudPresentation.Desktop, + selected = NextcloudDestination.Apps, + desktopWorkspaceKind = NextcloudDesktopWorkspaceKind.AppWorkspace, + onSelected = {}, identity = null, activeAppId = app.value, content = workspace, + ) + }) { + click("Collapse sidebar") + click("Change Files state") + assertTrue(has("Files state: 1")) + app.value = "photos" settle() - repeat(8) { fullScreen.value = !fullScreen.value; settle(); assertEquals(7, observed) } + assertTrue(has("Expand sidebar"), "The sidebar must remain collapsed after switching apps") + assertTrue(has("Files state: 0"), "A different app must start with independent content state") app.value = "files" settle() - assertEquals(0, observed) - app.value = "photos" + assertTrue(has("Expand sidebar")) + assertTrue(has("Files state: 1")) + } + } + + @Test fun adaptiveReparentingAndImmersiveNavigationRestoreTheScreenSubtree() { + val showShell = mutableStateOf(true) + val route = mutableStateOf(Screen.Files("/")) + nativeSceneTest(390, 800, content = { + val screen = route.value + val workspace = rememberAppWorkspaceContent(rememberSaveableStateHolder(), "files", screen) { + SavedCounter( + if (screen is Screen.MediaViewer) "Viewer" else "Files", + if (screen is Screen.MediaViewer) screen.navigationKey else "workspace", + ) + } + if (showShell.value) { + RootShell( + presentation = NextcloudPresentation.Adaptive, + selected = NextcloudDestination.Apps, + onSelected = {}, identity = null, activeAppId = "files", content = workspace, + ) + } else workspace() + }) { + click("Change Files state") + repeat(8) { + showShell.value = !showShell.value + settle() + assertTrue(has("Files state: 1"), "Layout changes must retain the moving screen subtree") + } + route.value = Screen.MediaViewer("synthetic-media", 0, 0, Screen.Files("/")) + showShell.value = false settle() - assertEquals(7, observed) - } finally { - composition.dispose() - recomposer.close() - runner.join() + assertTrue(has("Viewer state: 0"), "Expected isolated viewer state; rendered text: ${nodes().mapNotNull { it.config.getOrNull(SemanticsProperties.Text) }}") + click("Change Viewer state") + route.value = Screen.Files("/") + showShell.value = true + settle() + assertTrue(has("Files state: 1"), "Returning from the viewer must restore the Files screen state") + route.value = Screen.MediaViewer("another-synthetic-media", 0, 0, Screen.Files("/")) + showShell.value = false + settle() + assertTrue(has("Viewer state: 0"), "A different record must not restore the previous detail state") } } - private class EmptyApplier : AbstractApplier(Unit) { - override fun insertBottomUp(index: Int, instance: Unit) = Unit - override fun insertTopDown(index: Int, instance: Unit) = Unit - override fun move(from: Int, to: Int, count: Int) = Unit - override fun remove(index: Int, count: Int) = Unit - override fun onClear() = Unit + @Composable + private fun SavedCounter(label: String, recordKey: String = "workspace") { + var value by rememberSaveable(recordKey) { mutableStateOf(0) } + Column { + Text("$label state: $value") + Button(onClick = { value += 1 }) { Text("Change $label state") } + } } } From f4487d5f0dc54fb5256a00a63d11ad8fec959d2a Mon Sep 17 00:00:00 2001 From: "obiente-automations[bot]" <311907242+obiente-automations[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 02:31:59 +0000 Subject: [PATCH 11/13] chore(website): refresh marketing captures --- website/public/screenshots/capture-manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/public/screenshots/capture-manifest.json b/website/public/screenshots/capture-manifest.json index 4443b5c9f..d06000eed 100644 --- a/website/public/screenshots/capture-manifest.json +++ b/website/public/screenshots/capture-manifest.json @@ -441,7 +441,7 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceNavigationMemory.kt": "2a48ec4fda7ca47657253891e880a4169b16dbc83c197808532a92169961d569", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePins.kt": "91842afe8be45598eca9f23da7012401c53c6571737d1a9b38e82332ce84123c", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePresentation.kt": "e5655f80ca80cdb88ac49a54554e7d4cf684293efb2af4e291f0ef4b34529594", - "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt": "aa277dd2b3da172ad4121470fb0fd5d3d2f4f52e3a2fdadbeac64713cebed800", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt": "5489d5f40f8ec23b6d4575c376548a746d442262db64d51ef462b31eef268c75", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppsWorkspace.kt": "87b5ee4b871c85d55a8f43f3cb07ee619672def16fade42f819d67c913ac5295", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDateTimeFields.kt": "dbd41b992d9ebf24f76e6e66b03c8de23cf76267eca831eee318e5a582231d81", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDialogSurface.kt": "c571d170ab27cbe0bf02ecc4ae81b641b89fa6410244a7ce79edc46cc49101e1", @@ -628,7 +628,7 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudLinkRouting.kt": "5b29a90b69bb32aba118ef6c8b3f9d6eb26c03835823119b4a0f5bb1c1f4cb17", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudMediaViewer.kt": "fb424bf8979ac292ef30daba64e905e6f5123066cda793aa0244907e11b465c9", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudMediaViewerActions.kt": "48aaed6948d1423113d300cc3ab86d244ab76e8225e24988e9855edf74553944", - "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt": "5fa9d4de759c046f412d8d05e19e3e27aec92c66d4e6f62e74bf9c4152943b8a", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt": "1d54a59039654fbc7307fac38d33a37bba3e6ebad380c3da924f676d06df0822", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNotes.kt": "b53e2505663f0957dea9ad231b006d03d08f0405dc459974d85eb4768ef03484", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNotesCache.kt": "ac2206703b224364c1a3ff4097026c9c81d856042c20d31e5c28358016c3062d", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudPeople.kt": "cff910ea2cc77211ef81779c49ee0c957851f2b4a3ed32b857b12ded1cee643b", From 0f1d489e19292f9b54f17c1807c376693520b383 Mon Sep 17 00:00:00 2001 From: "obiente-automations[bot]" <311907242+obiente-automations[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 03:44:18 +0000 Subject: [PATCH 12/13] chore(website): refresh marketing captures --- website/public/screenshots/capture-manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/public/screenshots/capture-manifest.json b/website/public/screenshots/capture-manifest.json index 050b1eeec..048650d48 100644 --- a/website/public/screenshots/capture-manifest.json +++ b/website/public/screenshots/capture-manifest.json @@ -655,8 +655,8 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudPlatform.kt": "5370183fb78190a6893e4a36638b0570de4d2d6b33acab18aef52f2b83d83589", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudResponseLimits.kt": "fead6cbf4f723ea46f99c9155b190967205d24f198687b7bfd5a213c474374b8", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudSession.kt": "792a381fd5eefc44e13eb73ee95a80f8d52e8dcec9d3876ba06ec5392b5c1f81", - "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudSessionLoading.kt": "12339db06d73f7bb689de6cbf4a4a2ec8e1ffe8c6f10ca627390829b3907a716", - "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudStatusMessages.kt": "9236bf9c8f959d794e5756ac260232e24a6fb4e9372bb04cc6d2028d71f1ec05", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudSessionLoading.kt": "df00a758c8a29e9d1b221911a03f31547dc3510816da6228f8d38c607d0f4092", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudStatusMessages.kt": "898debe4e1a95d9b5bd936ac6edd435f0edb64049a58714a24df6661c310885b", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NotesApi.kt": "e3c685b02592ea791b0c9a478a098fc5bf428893aa4764110c793a78040f341e", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NotesFolderOperations.kt": "db6a04cfcd3b25b17c996cdc6dba21847d14f5970e06e6c24a3e2c85139dbe1d", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/OfficeDocumentWorkflow.kt": "2ddc486b9a948ed1ff5de48d64471df54d55f2bdb055e2446da79b5fb656cdf3", From 0b1a1adf5c60925f21d356f4487fe18d4af66efb Mon Sep 17 00:00:00 2001 From: "obiente-automations[bot]" <311907242+obiente-automations[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2026 04:01:48 +0000 Subject: [PATCH 13/13] chore(website): refresh marketing captures --- website/public/screenshots/capture-manifest.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/public/screenshots/capture-manifest.json b/website/public/screenshots/capture-manifest.json index 048650d48..d4aa79b5f 100644 --- a/website/public/screenshots/capture-manifest.json +++ b/website/public/screenshots/capture-manifest.json @@ -29,6 +29,7 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceNavigationMemory.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePins.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePresentation.kt", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppsWorkspace.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDateTimeFields.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDialogSurface.kt", @@ -455,6 +456,7 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceNavigationMemory.kt": "2a48ec4fda7ca47657253891e880a4169b16dbc83c197808532a92169961d569", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePins.kt": "91842afe8be45598eca9f23da7012401c53c6571737d1a9b38e82332ce84123c", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePresentation.kt": "e5655f80ca80cdb88ac49a54554e7d4cf684293efb2af4e291f0ef4b34529594", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt": "5489d5f40f8ec23b6d4575c376548a746d442262db64d51ef462b31eef268c75", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppsWorkspace.kt": "87b5ee4b871c85d55a8f43f3cb07ee619672def16fade42f819d67c913ac5295", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDateTimeFields.kt": "dbd41b992d9ebf24f76e6e66b03c8de23cf76267eca831eee318e5a582231d81", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDialogSurface.kt": "c571d170ab27cbe0bf02ecc4ae81b641b89fa6410244a7ce79edc46cc49101e1", @@ -647,7 +649,7 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudLinkRouting.kt": "5b29a90b69bb32aba118ef6c8b3f9d6eb26c03835823119b4a0f5bb1c1f4cb17", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudMediaViewer.kt": "fb424bf8979ac292ef30daba64e905e6f5123066cda793aa0244907e11b465c9", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudMediaViewerActions.kt": "48aaed6948d1423113d300cc3ab86d244ab76e8225e24988e9855edf74553944", - "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt": "d73abfae9c2da3525c01a24c3477f0ee0cd36e230eea7da0f0ea9f821eada192", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt": "8707f03ada4353bd0249acdbde2eeb50a76edd62d471703deeab924862fa082d", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNotes.kt": "29b0b80824eeb2d154f52a9eacc10bd4d7068b7eca3bd9427aec67903bf6ee0d", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNotesCache.kt": "aa9ec330c3a569e2bc9948858ddde1d939358069d38fbd7a0319b1ecc3baa363", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudPeople.kt": "cff910ea2cc77211ef81779c49ee0c957851f2b4a3ed32b857b12ded1cee643b",