Files
mantra-kmp/gradle/libs.versions.toml

106 lines
7.4 KiB
TOML
Raw Normal View History

2026-03-23 01:41:39 +02:00
[versions]
build: unify the composite on AGP 9.4.0 so Android Studio can sync Android Studio refused to sync the project after the lightning-kmp-app bump in 4e939c4: Using multiple versions of the Android Gradle Plugin [9.1.1, 9.4.0] across Gradle builds is not allowed. Affected builds: [:, :lightning-kmp-app:lightning-kmp:bitcoin-kmp:secp256k1-kmp] A composite build has to settle on a single AGP version across every build that applies the plugin. Four builds in this tree do: : 9.1.1 version catalog :lightning-kmp-app 9.1.1 version catalog :secp256k1-frost-kmp 9.1.1 version catalog :lightning-kmp-app:lightning-kmp:bitcoin-kmp:secp256k1-kmp 9.4.0 hardcoded lightning-kmp and bitcoin-kmp apply no android plugin at all, which is why the message names only the two ends of the chain rather than everything between them. secp256k1-kmp pins 9.4.0 twice -- a buildscript classpath entry, and a pluginManagement resolutionStrategy that rewrites every requested com.android plugin to that version -- so it cannot be talked out of it from here. The three catalogs move up to meet it. Pinning it down to 9.1.1 instead would mean editing a build four submodules deep and then committing in bitcoin-kmp and lightning-kmp purely to carry the pointer, for no gain. Moving up is also the direction the chain already set: bbba08b's f22c30a moved every build in it onto Gradle 9.7.1 for the same reason, and 4e939c4 followed. Submodule commits carried here: lightning-kmp-app bbba08b -> ce1ed01 build: move to AGP 9.4.0, matching the version the submodule chain pins secp256k1-frost-kmp d3b294d -> 80a55b7 build: move to AGP 9.4.0 to match the rest of mantra-kmp's composite Both sit on branches (build/agp-9.4.0 and build/gate-ios-targets-on-macos) rather than their masters, and neither is pushed yet, so a fresh clone cannot resolve these two pointers until they are. 9.4.0 is a minor bump inside the AGP 9.x line, so the DSL is unchanged and every android.* flag in gradle.properties -- newDsl, builtInKotlin, the r8 settings -- keeps its meaning. `:composeApp:compileDebugKotlinAndroid` passes clean. Note that this is only what the IDE needs to sync, not everything worth knowing about the chain. bitcoin-kmp's settings.gradle.kts substitutes secp256k1-kmp, -jni-jvm and the three -jni-jvm-{linux,darwin,mingw} coordinates, but not -jni-android. So the android app still resolves secp256k1-kmp-jni-android from maven central at stock 0.24.0, without the FROST/prefractal native modules the threshold branch exists to provide, and a call into those from android would fail with UnsatisfiedLinkError. Fixing that needs a substitution rule in bitcoin-kmp. Untouched here because it is a runtime concern, not a sync one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:58:32 +02:00
agp = "9.4.0"
2026-05-17 23:06:12 +02:00
android-compileSdk = "37"
2026-03-23 02:21:18 +02:00
android-minSdk = "26"
2026-05-17 23:06:12 +02:00
android-targetSdk = "37"
2026-03-30 13:21:14 +02:00
androidx-activity = "1.13.0"
2026-08-15 15:42:34 +02:00
androidx-appcompat = "1.8.0"
2026-06-05 23:55:44 +02:00
androidx-core = "1.19.0"
2026-03-23 01:41:39 +02:00
androidx-espresso = "3.7.0"
2026-08-15 15:42:34 +02:00
androidx-lifecycle = "2.11.0"
2026-03-23 01:41:39 +02:00
androidx-testExt = "1.3.0"
2026-08-15 15:42:34 +02:00
coilCompose = "3.5.0"
composeHotReload = "1.2.0"
composeMultiplatform = "1.11.1"
2026-04-18 21:58:32 +02:00
datastorePreferences = "1.2.1"
2026-03-23 01:41:39 +02:00
junit = "4.13.2"
2026-03-30 13:21:14 +02:00
kermit = "2.1.0"
2026-08-15 15:42:34 +02:00
kotlin = "2.4.10"
2026-05-17 23:12:49 +02:00
kotlinx-coroutines = "1.11.0"
kotlinx-datetime = "0.8.0"
2026-06-15 14:39:46 +02:00
kotlinxSerialization = "1.11.0"
2026-03-24 04:47:19 +02:00
ksp = "2.3.6"
2026-08-15 15:42:34 +02:00
ktor = "3.5.2"
lightningKmpCore = "1.11.5"
2026-03-23 01:41:39 +02:00
material3 = "1.10.0-alpha05"
feat: give the app the five breakpoints, and let the screen margin follow them Phase 6, steps 1 and 2. The app had no notion of window width at all -- two `BoxWithConstraints` in 30,000 lines of UI, both inside a view model -- so every layout decision in it was made once, for a phone, and then rendered unchanged into a 1800dp desktop window. **The dependency question the plan asked to settle first.** `material3-adaptive` publishes multiplatform under `org.jetbrains.compose.material3.adaptive`, with android, desktop and ios variants; the ios ones carry `ios_arm64` and `ios_simulator_arm64` attributes despite the `uikit*` artifact names, so the targets this project declares on a mac resolve. Version **1.2.0**, not the newer 1.3.0-beta02, because that is the version the pinned material3 itself resolves: `material3-adaptive-navigation-suite:1.10.0-alpha05` names `adaptive:1.2.0` in its pom, and 1.3.0 would pull window-core 1.5.0 in beside the 1.4.0 the pinned material3 compiled against. Nothing is lost by staying: 1.2.0 already computes the large and extra-large breakpoints through `supportLargeAndXLargeWidth`, and carries `ListDetailPaneScaffold` for the pane work. So steps 3-4 can use the library scaffolds rather than a hand-rolled equivalent. **`Breakpoint`** is the five-value enum -- compact / medium / expanded / large / extra-large at 0 / 600 / 840 / 1200 / 1600dp -- with `ofWidth` as a pure function so the thresholds are assertable without a Compose runtime. `TorchTheme` classifies once and provides `LocalBreakpoint`, so no two screens can disagree about the window they are both in. It reads `currentWindowDpSize()` rather than `currentWindowAdaptiveInfo()`. The latter also computes a `Posture` from the platform's fold state, which on android reaches for `WindowInfoTracker` and an activity; this call sits in `TorchTheme`, which wraps all 51 `@Preview` bodies in the tree, and a preview context is not an activity. The pane scaffolds ask for posture themselves, at the one place a fold changes the answer. **Spacing now adapts, and exactly one value moves.** M3 publishes a margin per breakpoint -- 16dp compact, 24dp everywhere wider -- and publishes nothing else that varies with window width. The scale itself is absolute: `space200` is 16dp on a phone and 16dp on a desktop, and what adapts is which token a job reaches for, not the token. So `screenMargin` goes 16 -> 24 at medium and holds there, and `containerPadding`, `itemGap` and the rest do not move -- a card does not become a different component because the window grew. Widening all of them is the "everything breathes on a big screen" instinct, and it reads as a zoomed phone rather than as a layout. A test asserts the non-movement, because that is the edit a later reviewer would wave through. Mechanically this made the eight semantic names constructor parameters instead of `get()`s over the scale, so a breakpoint can reassign one without moving the stop underneath it. Kotlin resolves a default expression against the parameters before it, so each still reads its stop by name and still follows it when the scale is overridden -- phase 2's `Spacing(space200 = 24.dp)` assertion holds unchanged. The two instances are singletons because `LocalSpacing` is a `staticCompositionLocalOf` and invalidates on identity, not equality. **A test found a real defect while being written.** `ofWidth` was `entries.last { width >= it.minWidth }`, which throws `NoSuchElementException` below 0dp. A desktop window reports a zero size for the frame before its first layout pass, and this is called from the theme on every composition, so the crash would have arrived on a resize rather than on anything a user did. Now total. `:composeApp:compileDebugKotlinAndroid` and `:composeApp:compileKotlinJvm` both green; 28 theme tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 07:15:43 +02:00
# The version material3 1.10.0-alpha05 itself resolves: its
# material3-adaptive-navigation-suite pom asks for adaptive 1.2.0. Picking the newer
# 1.3.0-beta02 would drag window-core 1.5.0 in beside the 1.4.0 the pinned material3
# compiled against, for a large/extra-large breakpoint pair that 1.2.0 already computes
# through `supportLargeAndXLargeWidth`.
material3Adaptive = "1.2.0"
2026-03-23 01:41:39 +02:00
materialIconsCore = "1.7.3"
materialIconsExtended = "1.7.3"
navigationCompose = "2.9.2"
2026-08-15 15:42:34 +02:00
okio = "3.18.1"
2026-07-01 15:11:31 +02:00
qrose = "1.1.2"
2026-08-29 16:22:56 +02:00
quartz = "1.14.0"
2026-08-15 15:42:34 +02:00
room3 = "3.0.1"
2026-06-15 14:39:46 +02:00
sqldelight = "2.3.2"
2026-08-15 15:42:34 +02:00
sqlite = "2.7.0"
2026-03-23 01:41:39 +02:00
[libraries]
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-testExt-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-testExt" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
2026-04-19 04:30:29 +02:00
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastorePreferences" }
2026-04-18 21:58:32 +02:00
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
2026-03-24 04:47:19 +02:00
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
build: phase 0 of the jvm target -- clear the ground, correct the plan First phase of docs/jvm-target.md. Nothing here turns the target on; it removes what would break the moment it is turned on, and stages the two catalog entries that cannot be derived automatically. Two of the four steps as written in the doc turned out to be wrong, and implementing them is how that surfaced -- both are corrected in the doc in this commit. **Deleted the stale jvmMain tree.** Six files under composeApp/src/jvmMain/kotlin/ac/cord/auxiliary/ survived from the Aux project this codebase grew out of. They have gone unnoticed because `jvmMain` is currently an orphan source set -- the accessor creates it, no target compiles it -- so the wrong package, the Room 2 imports (androidx.room, not androidx.room3), and the references to a long-gone AuxDatabase and AuxGlobal have never had to resolve. They would all become compile errors in phase 4. They are not lost: they are the closest thing to a skeleton for five of the six platform actuals phase 4 needs, and main.kt is a reasonable starting shape for the phase 5 desktop entry point. `git show HEAD~1` has them. **Added two catalog entries, not four.** sqlite-bundled-jvm and sqldelight-sqlite-driver. Both earn their place by being unreachable otherwise: sqlite-bundled-jvm has to be named explicitly because variant-aware resolution hands the *android* artifact to anything running on the host, and sqldelight-sqlite-driver is the jvm counterpart to the android-driver and native-driver entries already there. The doc also listed room3-runtime-jvm and sqldelight-jdbc-driver. Neither is right. Once jvm() exists, commonMain's existing androidx-room3-runtime resolves to the -jvm variant on its own, so an explicit entry is redundant and would drift. And the SQLDelight drivers phase 2 needs are for DbFactory, which lives in lightning-kmp-app -- a separate gradle build with its own version catalog, where an entry here is simply not visible. **kspJvm cannot be wired yet, and the build file already said so.** The doc's phase 0 told you to uncomment composeApp/build.gradle.kts:194. It contradicted its own phase 4, which is where jvm() gets turned on. The comment three lines above it states the rule: These configurations only exist when the ios targets are declared, which the kotlin block above does only on a mac. The same holds for kspJvm -- `dependencies { add("kspJvm", ...) }` throws UnknownConfigurationException until a jvm() target creates the configuration. So it moves into phase 4, into the same edit that declares the target. composeApp/build.gradle.kts is deliberately untouched by this commit. **Also documented: gradle does not run in a worktree here at all** until the submodule is checked out, which worktrees do not do automatically. `lightning-kmp-app/` is empty and configuration fails with "Project with path ':library' not found in build ':lightning-kmp-app'". Recorded in the phase 0 verification section along with the caveat that a linked worktree shares .git/modules/ with the main checkout, so both trees end up on one submodule git dir. **Not verified by a build.** For that reason. The deletion is an orphan source set and the additions are unreferenced catalog lines, so neither can change a build's outcome -- but that is an argument, not a green check, and it is the second commit in a row on this branch that has not compiled anything. Phase 4 is the first phase that genuinely cannot be done without a working gradle invocation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 00:44:10 +02:00
androidx-sqlite-bundled-jvm = { module = "androidx.sqlite:sqlite-bundled-jvm", version.ref = "sqlite" }
2026-05-02 00:05:38 +02:00
androidx-room3-runtime = { module = "androidx.room3:room3-runtime", version.ref = "room3" }
androidx-room3-compiler = { module = "androidx.room3:room3-compiler", version.ref = "room3" }
2026-05-20 21:17:47 +02:00
androidx-room3-sqlite-wrapper = { module = "androidx.room3:room3-sqlite-wrapper", version.ref = "room3" }
2026-03-23 01:41:39 +02:00
androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
2026-04-21 23:49:54 +02:00
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" }
coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coilCompose" }
2026-03-30 13:14:37 +02:00
compose-components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "composeMultiplatform" }
2026-03-23 01:41:39 +02:00
compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" }
compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" }
compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "material3" }
feat: give the app the five breakpoints, and let the screen margin follow them Phase 6, steps 1 and 2. The app had no notion of window width at all -- two `BoxWithConstraints` in 30,000 lines of UI, both inside a view model -- so every layout decision in it was made once, for a phone, and then rendered unchanged into a 1800dp desktop window. **The dependency question the plan asked to settle first.** `material3-adaptive` publishes multiplatform under `org.jetbrains.compose.material3.adaptive`, with android, desktop and ios variants; the ios ones carry `ios_arm64` and `ios_simulator_arm64` attributes despite the `uikit*` artifact names, so the targets this project declares on a mac resolve. Version **1.2.0**, not the newer 1.3.0-beta02, because that is the version the pinned material3 itself resolves: `material3-adaptive-navigation-suite:1.10.0-alpha05` names `adaptive:1.2.0` in its pom, and 1.3.0 would pull window-core 1.5.0 in beside the 1.4.0 the pinned material3 compiled against. Nothing is lost by staying: 1.2.0 already computes the large and extra-large breakpoints through `supportLargeAndXLargeWidth`, and carries `ListDetailPaneScaffold` for the pane work. So steps 3-4 can use the library scaffolds rather than a hand-rolled equivalent. **`Breakpoint`** is the five-value enum -- compact / medium / expanded / large / extra-large at 0 / 600 / 840 / 1200 / 1600dp -- with `ofWidth` as a pure function so the thresholds are assertable without a Compose runtime. `TorchTheme` classifies once and provides `LocalBreakpoint`, so no two screens can disagree about the window they are both in. It reads `currentWindowDpSize()` rather than `currentWindowAdaptiveInfo()`. The latter also computes a `Posture` from the platform's fold state, which on android reaches for `WindowInfoTracker` and an activity; this call sits in `TorchTheme`, which wraps all 51 `@Preview` bodies in the tree, and a preview context is not an activity. The pane scaffolds ask for posture themselves, at the one place a fold changes the answer. **Spacing now adapts, and exactly one value moves.** M3 publishes a margin per breakpoint -- 16dp compact, 24dp everywhere wider -- and publishes nothing else that varies with window width. The scale itself is absolute: `space200` is 16dp on a phone and 16dp on a desktop, and what adapts is which token a job reaches for, not the token. So `screenMargin` goes 16 -> 24 at medium and holds there, and `containerPadding`, `itemGap` and the rest do not move -- a card does not become a different component because the window grew. Widening all of them is the "everything breathes on a big screen" instinct, and it reads as a zoomed phone rather than as a layout. A test asserts the non-movement, because that is the edit a later reviewer would wave through. Mechanically this made the eight semantic names constructor parameters instead of `get()`s over the scale, so a breakpoint can reassign one without moving the stop underneath it. Kotlin resolves a default expression against the parameters before it, so each still reads its stop by name and still follows it when the scale is overridden -- phase 2's `Spacing(space200 = 24.dp)` assertion holds unchanged. The two instances are singletons because `LocalSpacing` is a `staticCompositionLocalOf` and invalidates on identity, not equality. **A test found a real defect while being written.** `ofWidth` was `entries.last { width >= it.minWidth }`, which throws `NoSuchElementException` below 0dp. A desktop window reports a zero size for the frame before its first layout pass, and this is called from the theme on every composition, so the crash would have arrived on a resize rather than on anything a user did. Now total. `:composeApp:compileDebugKotlinAndroid` and `:composeApp:compileKotlinJvm` both green; 28 theme tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 07:15:43 +02:00
compose-material3-adaptive = { module = "org.jetbrains.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
feat: give the app a navigation component, and stop the app bar duplicating it Phase 6, step 3. The app had no navigation component of any kind: 43 screens reached by pushing a route, and one home screen whose top app bar carried the only two peer surfaces -- a profile avatar in the leading slot, a search icon in the trailing one. **This is an information-architecture change and was taken as one.** With a single top-level destination, a navigation bar would have held one item and been strictly worse than the app bar it replaced -- M3's caution is to swap only functionally equivalent components. Promoting search and profile to peer destinations is what makes a navigation component mean anything here, and it was put to the product owner rather than inferred. Answered: promote them. The consequence is in `HomeScreen`: the app bar now carries a title and nothing else. Two routes to one destination is the thing the caution is about, and the navigation component is now the one route, at every breakpoint. **Which component, at which breakpoint**, straight from the layout foundation: | compact | navigation bar | | medium, expanded | collapsed rail | | large, extra-large | expanded rail | `NavigationSuiteScaffoldDefaults.navigationSuiteType` is not used, and the difference is the last row -- it stops at the collapsed rail, because it classifies with the three-value window size class rather than the five breakpoints the May 2026 revision published. Deriving from `Breakpoint` reaches the row the library's default cannot, and keeps one source of truth for window width in the app. `NavigationSuiteType.None` on everything else. A navigation bar belongs on the destinations it switches between; on a chat room, a signing screen or an onboarding step -- pushed to and left by coming back -- it is a permanent invitation to lose your place. **Two things the wiring needed.** `ActiveProfileRoute` is addressed by metadata event id, not by public key, and only the home screen ever had one. The nav host now observes it for as long as a key is signed in, and the profile item is *disabled* until it arrives rather than absent -- an item that appears late moves the two beside it, and a bar whose items move under a thumb is worse than one briefly unavailable. The item click pops to `HomeRoute`, not to the graph's start destination. The android docs give the second shape and it would be wrong here: this graph starts at `LoadingRoute`, and onboarding clears the stack with `popUpTo(0)` on its way to home, so by the time these items exist the start destination is not on the stack at all -- popping to it would leave the loading screen underneath as the thing back returns to. **Tests, and one that could not be written.** The breakpoint-to-component table is a pure function so all five rows are asserted; the two rail rows differ only in whether labels are drawn, and nobody opens a 1200dp window on purpose. Four more compose the component around a real nav graph, because `TopLevelDestination.of` matches by `hasRoute` -- reflection over the serialized route -- and a renamed route would fail by never showing the component at all. Navigation in those is driven through the controller rather than by tapping an item. That is a harness limitation, established rather than assumed: a click handler that navigates trips navigation-compose's own main-thread assertion under `runDesktopComposeUiTest`, reproducible in twenty lines containing no app code -- a `NavHost`, two routes and a `TextButton`. What an item's `onClick` builds is asserted where it is a pure function instead. Also `material3-adaptive-navigation-suite`, versioned with material3 rather than with the adaptive library: it is published by the material3 group, and its 1.10.0-alpha05 is what names adaptive 1.2.0 in the first place. Most of the `MantraNavHost` diff is indentation -- the `NavHost` call gained an enclosing composable. `git diff -w` shows the 38 lines that are not. 626 jvm tests green; android compiles. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 07:53:27 +02:00
# Versioned with material3 rather than with material3Adaptive: it is published by the
# material3 group and its 1.10.0-alpha05 is what names adaptive 1.2.0 in the first place.
compose-material3-adaptive-navigation-suite = { module = "org.jetbrains.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3" }
2026-03-23 02:21:18 +02:00
compose-material-icons-core = { module = "org.jetbrains.compose.material:material-icons-core", version.ref = "materialIconsCore" }
compose-material-icons-extended = { module = "org.jetbrains.compose.material:material-icons-extended", version.ref = "materialIconsExtended" }
2026-03-23 01:41:39 +02:00
compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "composeMultiplatform" }
2026-03-30 13:14:37 +02:00
compose-uiTooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "composeMultiplatform" }
2026-03-23 01:41:39 +02:00
compose-uiToolingPreview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "composeMultiplatform" }
2026-04-21 23:49:54 +02:00
junit = { module = "junit:junit", version.ref = "junit" }
2026-03-23 01:41:39 +02:00
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
2026-04-21 23:49:54 +02:00
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
2026-03-23 01:41:39 +02:00
kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
feat: read a room's group events again when they arrived out of order Relays impose no ordering, so a kind:445 can turn up before the group can read it: an application message encrypted under an epoch whose commit has not landed, or a commit for an epoch ahead of the local one. Both are stored and then dropped -- MarmotInboundManager refuses an out-of-epoch commit precisely so it does not half-mutate the group -- and nothing goes back for them once the missing event fills the gap. The message is on disk, readable, and never read. A "Reindex Events" button at the bottom of the group's detail screen is that second look. Only events with nothing to show for them are replayed: no chat line at all, or one of the two placeholder types. A room where nothing went wrong is left exactly as it was, which is what makes the button safe to press on a hunch. Passes repeat while a pass recovers something, because created_at order is not epoch order and a commit recovered by one pass is what lets the next read the messages that were waiting on it. **Replaying was not safe as it stood.** Every row the path writes is keyed on an event id and upserts in place -- MarmotGroupEvent, MarmotInnerEvent, and the nip30303 entities -- with one exception. ChatMessage's primary key is autogenerated, so writing a freshly built line always inserts, and a re-read would have left the room showing each recovered message twice, once as "Undecryptable Message" and once as itself. ChatMessage.reconcileMarmotLine matches on the group event id instead, so a re-read is an update, and refuses to let a placeholder overwrite a line that says something. That last rule is what protects the line this device wrote on the way out for a message it sent: our own kind:445 cannot be read back, since the sender ratchet has consumed the generation, and without the rule a replay would have replaced our words with "Undecryptable Message". The MLS group itself was already safe to replay against, which is worth saying because it is the part that looks dangerous: a commit behind the current epoch is rejected as a duplicate before it touches the group, one ahead is refused, and a consumed ratchet generation throws before mutating anything. The exception was quartz's EpochCommitTracker, which does not dedupe and only empties when a commit applies -- so replaying a held commit just grew the list and left it pending forever. forgetPendingCommits drops the room's entries first, and the sweep feeds the events back in the order CommitOrdering picks a winner in, so a contested epoch resolves the same way it would have on every other device. **What is testable, and what is not.** The DAO is not: testDebugUnitTest is plain JVM and Room's in-memory builder wants an Android Context. So the two pieces carrying decisions are lifted out where they can be run without one -- MarmotReindexSweep for the stopping rule, and reconcileMarmotLine for which of two lines wins -- and the DAO is left as query, sweep, write. The filter tests pin why the query's `tags LIKE` is a prefilter and not a test: an event belonging to another room can mention this one in a q tag, and its own h tag is what rejects it. **Not recovered by any of this.** A message whose key is gone -- one the ratchet has already advanced past, or one from an epoch predating this device's join. And events that never reached disk at all: storeNostrEvent is a single transaction, so a kind:445 arriving before its room exists rolls back its own insert along with the failed indexing, and only a re-sync brings it back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 01:49:57 +02:00
kotlinx-coroutinesTest = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
2026-03-30 13:09:49 +02:00
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
2026-06-15 14:39:46 +02:00
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
kotlinx-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kotlinxSerialization" }
2026-03-27 13:35:29 +02:00
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
2026-03-30 13:14:37 +02:00
ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
lightning-kmp-core = { module = "fr.acinq.lightning:lightning-kmp-core", version.ref = "lightningKmpCore" }
2026-03-23 01:41:39 +02:00
navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
2026-04-18 02:41:31 +02:00
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
2026-07-01 15:11:31 +02:00
qrose = { module = "io.github.alexzhirkevich:qrose", version.ref = "qrose" }
2026-06-15 14:39:46 +02:00
sqldelight-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
sqldelight-coroutines-extensions = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
sqldelight-native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" }
sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqldelight" }
build: phase 0 of the jvm target -- clear the ground, correct the plan First phase of docs/jvm-target.md. Nothing here turns the target on; it removes what would break the moment it is turned on, and stages the two catalog entries that cannot be derived automatically. Two of the four steps as written in the doc turned out to be wrong, and implementing them is how that surfaced -- both are corrected in the doc in this commit. **Deleted the stale jvmMain tree.** Six files under composeApp/src/jvmMain/kotlin/ac/cord/auxiliary/ survived from the Aux project this codebase grew out of. They have gone unnoticed because `jvmMain` is currently an orphan source set -- the accessor creates it, no target compiles it -- so the wrong package, the Room 2 imports (androidx.room, not androidx.room3), and the references to a long-gone AuxDatabase and AuxGlobal have never had to resolve. They would all become compile errors in phase 4. They are not lost: they are the closest thing to a skeleton for five of the six platform actuals phase 4 needs, and main.kt is a reasonable starting shape for the phase 5 desktop entry point. `git show HEAD~1` has them. **Added two catalog entries, not four.** sqlite-bundled-jvm and sqldelight-sqlite-driver. Both earn their place by being unreachable otherwise: sqlite-bundled-jvm has to be named explicitly because variant-aware resolution hands the *android* artifact to anything running on the host, and sqldelight-sqlite-driver is the jvm counterpart to the android-driver and native-driver entries already there. The doc also listed room3-runtime-jvm and sqldelight-jdbc-driver. Neither is right. Once jvm() exists, commonMain's existing androidx-room3-runtime resolves to the -jvm variant on its own, so an explicit entry is redundant and would drift. And the SQLDelight drivers phase 2 needs are for DbFactory, which lives in lightning-kmp-app -- a separate gradle build with its own version catalog, where an entry here is simply not visible. **kspJvm cannot be wired yet, and the build file already said so.** The doc's phase 0 told you to uncomment composeApp/build.gradle.kts:194. It contradicted its own phase 4, which is where jvm() gets turned on. The comment three lines above it states the rule: These configurations only exist when the ios targets are declared, which the kotlin block above does only on a mac. The same holds for kspJvm -- `dependencies { add("kspJvm", ...) }` throws UnknownConfigurationException until a jvm() target creates the configuration. So it moves into phase 4, into the same edit that declares the target. composeApp/build.gradle.kts is deliberately untouched by this commit. **Also documented: gradle does not run in a worktree here at all** until the submodule is checked out, which worktrees do not do automatically. `lightning-kmp-app/` is empty and configuration fails with "Project with path ':library' not found in build ':lightning-kmp-app'". Recorded in the phase 0 verification section along with the caveat that a linked worktree shares .git/modules/ with the main checkout, so both trees end up on one submodule git dir. **Not verified by a build.** For that reason. The deletion is an orphan source set and the additions are unreferenced catalog lines, so neither can change a build's outcome -- but that is an argument, not a green check, and it is the second commit in a row on this branch that has not compiled anything. Phase 4 is the first phase that genuinely cannot be done without a working gradle invocation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 00:44:10 +02:00
sqldelight-sqlite-driver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }
2026-03-30 13:14:37 +02:00
vitorpamplona-quartz = { module = "com.vitorpamplona.quartz:quartz", version.ref = "quartz" }
2026-03-23 01:41:39 +02:00
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
2026-05-02 00:05:38 +02:00
androidx-room3 = { id = "androidx.room3", version.ref = "room3" }
2026-03-23 01:41:39 +02:00
androidLibrary = { id = "com.android.library", version.ref = "agp" }
composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "composeHotReload" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
2026-03-23 02:21:18 +02:00
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
2026-03-24 04:47:19 +02:00
kotlinPluginSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
2026-06-15 14:39:46 +02:00
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }