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"
|
2026-04-21 23:50:54 +02:00
|
|
|
lightningKmpCore = "1.11.5"
|
2026-03-23 01:41:39 +02:00
|
|
|
material3 = "1.10.0-alpha05"
|
|
|
|
|
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" }
|
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" }
|
2026-03-30 13:25:19 +02:00
|
|
|
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" }
|
2026-04-21 23:50:54 +02:00
|
|
|
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" }
|