Skip to content

feat: FTS5 full-text message search#5373

Draft
jamesarich wants to merge 1 commit into
mainfrom
feat/fts5-message-search
Draft

feat: FTS5 full-text message search#5373
jamesarich wants to merge 1 commit into
mainfrom
feat/fts5-message-search

Conversation

@jamesarich
Copy link
Copy Markdown
Collaborator

@jamesarich jamesarich commented May 6, 2026

Summary

Adds full-text search to the messaging screen using SQLite FTS5, enabling users to find messages within a conversation.

Changes

Database Layer

  • PacketFts — new @Fts5(contentEntity = Packet::class) entity that mirrors message_text for full-text indexing
  • Packet.messageText — new column populated at insert time from DataPacket.text
  • PacketDao — FTS5 MATCH queries (searchMessages, searchMessagesInConversation), plus backfillMessageTexts() using json_extract for efficient one-shot backfill
  • Schema v39 — AutoMigration from v38

Repository Layer

  • PacketRepository.searchMessages() — sanitizes user input (double-quote wrapping per token), scopes to contact key optionally
  • Populates messageText at insert time in both message save paths

UI Layer

  • MessageSearchBar — M3 contextual search bar with text field, "X of Y" counter, prev/next arrows, and clear button
  • Search result navigationsearchResultIndex StateFlow with navigateToNext/Previous, scroll-to-match via LaunchedEffect
  • Term highlightingHighlightedText composable highlights matching tokens in message bubbles using tertiaryContainer color
  • 300ms debounce with 2-char minimum threshold

Backfill Strategy

On database switch, a single SQL UPDATE with json_extract(data, "$.text") populates message_text for historical packets, followed by an FTS5 rebuild command. Zero rows loaded into memory.

Screenshots

TODO: Add screenshots of search bar and highlighted results

Testing

  • Search finds messages by partial text match
  • Prev/next navigation scrolls to correct message
  • Matching terms highlighted in bubble text
  • New messages are immediately searchable
  • Historical messages searchable after backfill
  • Search handles special characters gracefully (quotes, asterisks, etc.)

@github-actions github-actions Bot added the enhancement New feature or request label May 6, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 6, 2026

❌ 11 Tests Failed:

Tests completed Failed Passed Skipped
2511 11 2500 0
View the top 3 failed test(s) by shortest run time
org.meshtastic.core.database.dao.QuickChatActionDaoTest::testDeleteAll
Stack Traces | 0.14s run time
android.database.sqlite.SQLiteException: no such module: FTS5 (code 1 SQLITE_ERROR)
	at org.robolectric.nativeruntime.SQLiteConnectionNatives.nativeExecute(Native Method)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.lambda$nativeExecute$10(ShadowNativeSQLiteConnection.java:132)
	at org.robolectric.util.PerfStatsCollector.measure(PerfStatsCollector.java:83)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.nativeExecute(ShadowNativeSQLiteConnection.java:130)
	at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
	at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:730)
	at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:621)
	at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:47)
	at androidx.sqlite.db.framework.FrameworkSQLiteStatement.execute(FrameworkSQLiteStatement.android.kt:30)
	at androidx.sqlite.driver.SupportSQLiteStatement$OtherSQLiteStatement.step(SupportSQLiteStatement.android.kt:540)
	at androidx.sqlite.SQLite__SQLite_nonWebKt.execSQL(SQLite.nonWeb.kt:26)
	at androidx.sqlite.SQLite.execSQL(Unknown Source)
	at org.meshtastic.core.database.MeshtasticDatabase_Impl$createOpenDelegate$_openDelegate$1.createAllTables(MeshtasticDatabase_Impl.kt:96)
	at androidx.room3.BaseRoomConnectionManager.onCreate(RoomConnectionManager.kt:190)
	at androidx.room3.BaseRoomConnectionManager.configureDatabase(RoomConnectionManager.kt:130)
	at androidx.room3.BaseRoomConnectionManager.access$configureDatabase(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invokeSuspend(ExclusiveMutex.kt:97)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invoke(ExclusiveMutex.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.BaseRoomConnectionManager.openLocked(RoomConnectionManager.kt:367)
	at androidx.room3.BaseRoomConnectionManager.access$openLocked(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invokeSuspend(RoomConnectionManager.kt:64)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invoke(RoomConnectionManager.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invokeSuspend(PassthroughConnectionPool.kt:78)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invokeSuspend(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.PassthroughConnectionPool.withInitializationLock(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool.useConnection(PassthroughConnectionPool.kt:76)
	at androidx.room3.RoomConnectionManager.useConnection(RoomConnectionManager.android.kt:99)
	at androidx.room3.RoomDatabase.useConnection(RoomDatabase.android.kt:408)
	at androidx.room3.util.DBUtil__DBUtilKt$performSuspending$2.invokeSuspend(DBUtil.kt:208)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:798)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
org.meshtastic.core.database.dao.QuickChatActionDaoTest::testPositionOrdering
Stack Traces | 0.19s run time
android.database.sqlite.SQLiteException: no such module: FTS5 (code 1 SQLITE_ERROR)
	at org.robolectric.nativeruntime.SQLiteConnectionNatives.nativeExecute(Native Method)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.lambda$nativeExecute$10(ShadowNativeSQLiteConnection.java:132)
	at org.robolectric.util.PerfStatsCollector.measure(PerfStatsCollector.java:83)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.nativeExecute(ShadowNativeSQLiteConnection.java:130)
	at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
	at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:730)
	at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:621)
	at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:47)
	at androidx.sqlite.db.framework.FrameworkSQLiteStatement.execute(FrameworkSQLiteStatement.android.kt:30)
	at androidx.sqlite.driver.SupportSQLiteStatement$OtherSQLiteStatement.step(SupportSQLiteStatement.android.kt:540)
	at androidx.sqlite.SQLite__SQLite_nonWebKt.execSQL(SQLite.nonWeb.kt:26)
	at androidx.sqlite.SQLite.execSQL(Unknown Source)
	at org.meshtastic.core.database.MeshtasticDatabase_Impl$createOpenDelegate$_openDelegate$1.createAllTables(MeshtasticDatabase_Impl.kt:96)
	at androidx.room3.BaseRoomConnectionManager.onCreate(RoomConnectionManager.kt:190)
	at androidx.room3.BaseRoomConnectionManager.configureDatabase(RoomConnectionManager.kt:130)
	at androidx.room3.BaseRoomConnectionManager.access$configureDatabase(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invokeSuspend(ExclusiveMutex.kt:97)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invoke(ExclusiveMutex.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.BaseRoomConnectionManager.openLocked(RoomConnectionManager.kt:367)
	at androidx.room3.BaseRoomConnectionManager.access$openLocked(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invokeSuspend(RoomConnectionManager.kt:64)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invoke(RoomConnectionManager.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invokeSuspend(PassthroughConnectionPool.kt:78)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invokeSuspend(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.PassthroughConnectionPool.withInitializationLock(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool.useConnection(PassthroughConnectionPool.kt:76)
	at androidx.room3.RoomConnectionManager.useConnection(RoomConnectionManager.android.kt:99)
	at androidx.room3.RoomDatabase.useConnection(RoomDatabase.android.kt:408)
	at androidx.room3.util.DBUtil__DBUtilKt$performSuspending$2.invokeSuspend(DBUtil.kt:208)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:798)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
org.meshtastic.core.database.dao.QuickChatActionDaoTest::testUpdateAction
Stack Traces | 0.199s run time
android.database.sqlite.SQLiteException: no such module: FTS5 (code 1 SQLITE_ERROR)
	at org.robolectric.nativeruntime.SQLiteConnectionNatives.nativeExecute(Native Method)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.lambda$nativeExecute$10(ShadowNativeSQLiteConnection.java:132)
	at org.robolectric.util.PerfStatsCollector.measure(PerfStatsCollector.java:83)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.nativeExecute(ShadowNativeSQLiteConnection.java:130)
	at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
	at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:730)
	at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:621)
	at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:47)
	at androidx.sqlite.db.framework.FrameworkSQLiteStatement.execute(FrameworkSQLiteStatement.android.kt:30)
	at androidx.sqlite.driver.SupportSQLiteStatement$OtherSQLiteStatement.step(SupportSQLiteStatement.android.kt:540)
	at androidx.sqlite.SQLite__SQLite_nonWebKt.execSQL(SQLite.nonWeb.kt:26)
	at androidx.sqlite.SQLite.execSQL(Unknown Source)
	at org.meshtastic.core.database.MeshtasticDatabase_Impl$createOpenDelegate$_openDelegate$1.createAllTables(MeshtasticDatabase_Impl.kt:96)
	at androidx.room3.BaseRoomConnectionManager.onCreate(RoomConnectionManager.kt:190)
	at androidx.room3.BaseRoomConnectionManager.configureDatabase(RoomConnectionManager.kt:130)
	at androidx.room3.BaseRoomConnectionManager.access$configureDatabase(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invokeSuspend(ExclusiveMutex.kt:97)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invoke(ExclusiveMutex.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.BaseRoomConnectionManager.openLocked(RoomConnectionManager.kt:367)
	at androidx.room3.BaseRoomConnectionManager.access$openLocked(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invokeSuspend(RoomConnectionManager.kt:64)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invoke(RoomConnectionManager.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invokeSuspend(PassthroughConnectionPool.kt:78)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invokeSuspend(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.PassthroughConnectionPool.withInitializationLock(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool.useConnection(PassthroughConnectionPool.kt:76)
	at androidx.room3.RoomConnectionManager.useConnection(RoomConnectionManager.android.kt:99)
	at androidx.room3.RoomDatabase.useConnection(RoomDatabase.android.kt:408)
	at androidx.room3.util.DBUtil__DBUtilKt$performSuspending$2.invokeSuspend(DBUtil.kt:208)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:798)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
org.meshtastic.core.database.dao.QuickChatActionDaoTest::testDeleteAction
Stack Traces | 0.2s run time
android.database.sqlite.SQLiteException: no such module: FTS5 (code 1 SQLITE_ERROR)
	at org.robolectric.nativeruntime.SQLiteConnectionNatives.nativeExecute(Native Method)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.lambda$nativeExecute$10(ShadowNativeSQLiteConnection.java:132)
	at org.robolectric.util.PerfStatsCollector.measure(PerfStatsCollector.java:83)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.nativeExecute(ShadowNativeSQLiteConnection.java:130)
	at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
	at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:730)
	at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:621)
	at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:47)
	at androidx.sqlite.db.framework.FrameworkSQLiteStatement.execute(FrameworkSQLiteStatement.android.kt:30)
	at androidx.sqlite.driver.SupportSQLiteStatement$OtherSQLiteStatement.step(SupportSQLiteStatement.android.kt:540)
	at androidx.sqlite.SQLite__SQLite_nonWebKt.execSQL(SQLite.nonWeb.kt:26)
	at androidx.sqlite.SQLite.execSQL(Unknown Source)
	at org.meshtastic.core.database.MeshtasticDatabase_Impl$createOpenDelegate$_openDelegate$1.createAllTables(MeshtasticDatabase_Impl.kt:96)
	at androidx.room3.BaseRoomConnectionManager.onCreate(RoomConnectionManager.kt:190)
	at androidx.room3.BaseRoomConnectionManager.configureDatabase(RoomConnectionManager.kt:130)
	at androidx.room3.BaseRoomConnectionManager.access$configureDatabase(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invokeSuspend(ExclusiveMutex.kt:97)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invoke(ExclusiveMutex.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.BaseRoomConnectionManager.openLocked(RoomConnectionManager.kt:367)
	at androidx.room3.BaseRoomConnectionManager.access$openLocked(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invokeSuspend(RoomConnectionManager.kt:64)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invoke(RoomConnectionManager.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invokeSuspend(PassthroughConnectionPool.kt:78)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invokeSuspend(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.PassthroughConnectionPool.withInitializationLock(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool.useConnection(PassthroughConnectionPool.kt:76)
	at androidx.room3.RoomConnectionManager.useConnection(RoomConnectionManager.android.kt:99)
	at androidx.room3.RoomDatabase.useConnection(RoomDatabase.android.kt:408)
	at androidx.room3.util.DBUtil__DBUtilKt$performSuspending$2.invokeSuspend(DBUtil.kt:208)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:798)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
org.meshtastic.core.database.dao.QuickChatActionDaoTest::testReactiveFlowEmitsUpdatesOnInsertAndDelete
Stack Traces | 0.305s run time
android.database.sqlite.SQLiteException: no such module: FTS5 (code 1 SQLITE_ERROR)
	at org.robolectric.nativeruntime.SQLiteConnectionNatives.nativeExecute(Native Method)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.lambda$nativeExecute$10(ShadowNativeSQLiteConnection.java:132)
	at org.robolectric.util.PerfStatsCollector.measure(PerfStatsCollector.java:83)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.nativeExecute(ShadowNativeSQLiteConnection.java:130)
	at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
	at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:730)
	at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:621)
	at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:47)
	at androidx.sqlite.db.framework.FrameworkSQLiteStatement.execute(FrameworkSQLiteStatement.android.kt:30)
	at androidx.sqlite.driver.SupportSQLiteStatement$OtherSQLiteStatement.step(SupportSQLiteStatement.android.kt:540)
	at androidx.sqlite.SQLite__SQLite_nonWebKt.execSQL(SQLite.nonWeb.kt:26)
	at androidx.sqlite.SQLite.execSQL(Unknown Source)
	at org.meshtastic.core.database.MeshtasticDatabase_Impl$createOpenDelegate$_openDelegate$1.createAllTables(MeshtasticDatabase_Impl.kt:96)
	at androidx.room3.BaseRoomConnectionManager.onCreate(RoomConnectionManager.kt:190)
	at androidx.room3.BaseRoomConnectionManager.configureDatabase(RoomConnectionManager.kt:130)
	at androidx.room3.BaseRoomConnectionManager.access$configureDatabase(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invokeSuspend(ExclusiveMutex.kt:97)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invoke(ExclusiveMutex.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.BaseRoomConnectionManager.openLocked(RoomConnectionManager.kt:367)
	at androidx.room3.BaseRoomConnectionManager.access$openLocked(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invokeSuspend(RoomConnectionManager.kt:64)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invoke(RoomConnectionManager.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invokeSuspend(PassthroughConnectionPool.kt:78)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invokeSuspend(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.PassthroughConnectionPool.withInitializationLock(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool.useConnection(PassthroughConnectionPool.kt:76)
	at androidx.room3.RoomConnectionManager.useConnection(RoomConnectionManager.android.kt:99)
	at androidx.room3.RoomDatabase.useConnection(RoomDatabase.android.kt:408)
	at androidx.room3.TriggerBasedInvalidationTracker.syncTriggers$room3_runtime(InvalidationTracker.kt:299)
	at androidx.room3.TriggerBasedInvalidationTracker$createFlow$1$1.invokeSuspend(InvalidationTracker.kt:239)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:798)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
org.meshtastic.core.database.dao.QuickChatActionDaoTest::testInsertActionAndRetrieveIt
Stack Traces | 18.7s run time
android.database.sqlite.SQLiteException: no such module: FTS5 (code 1 SQLITE_ERROR)
	at org.robolectric.nativeruntime.SQLiteConnectionNatives.nativeExecute(Native Method)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.lambda$nativeExecute$10(ShadowNativeSQLiteConnection.java:132)
	at org.robolectric.util.PerfStatsCollector.measure(PerfStatsCollector.java:83)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.nativeExecute(ShadowNativeSQLiteConnection.java:130)
	at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
	at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:730)
	at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:621)
	at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:47)
	at androidx.sqlite.db.framework.FrameworkSQLiteStatement.execute(FrameworkSQLiteStatement.android.kt:30)
	at androidx.sqlite.driver.SupportSQLiteStatement$OtherSQLiteStatement.step(SupportSQLiteStatement.android.kt:540)
	at androidx.sqlite.SQLite__SQLite_nonWebKt.execSQL(SQLite.nonWeb.kt:26)
	at androidx.sqlite.SQLite.execSQL(Unknown Source)
	at org.meshtastic.core.database.MeshtasticDatabase_Impl$createOpenDelegate$_openDelegate$1.createAllTables(MeshtasticDatabase_Impl.kt:96)
	at androidx.room3.BaseRoomConnectionManager.onCreate(RoomConnectionManager.kt:190)
	at androidx.room3.BaseRoomConnectionManager.configureDatabase(RoomConnectionManager.kt:130)
	at androidx.room3.BaseRoomConnectionManager.access$configureDatabase(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invokeSuspend(ExclusiveMutex.kt:97)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invoke(ExclusiveMutex.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.BaseRoomConnectionManager.openLocked(RoomConnectionManager.kt:367)
	at androidx.room3.BaseRoomConnectionManager.access$openLocked(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invokeSuspend(RoomConnectionManager.kt:64)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invoke(RoomConnectionManager.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invokeSuspend(PassthroughConnectionPool.kt:78)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invokeSuspend(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.PassthroughConnectionPool.withInitializationLock(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool.useConnection(PassthroughConnectionPool.kt:76)
	at androidx.room3.RoomConnectionManager.useConnection(RoomConnectionManager.android.kt:99)
	at androidx.room3.RoomDatabase.useConnection(RoomDatabase.android.kt:408)
	at androidx.room3.util.DBUtil__DBUtilKt$performSuspending$2.invokeSuspend(DBUtil.kt:208)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:798)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
org.meshtastic.app.ui.NavigationAssemblyTest::verifyNavigationGraphsAssembleWithoutCrashing
Stack Traces | 31.4s run time
android.database.sqlite.SQLiteException: no such module: FTS5 (code 1 SQLITE_ERROR)
	at org.robolectric.nativeruntime.SQLiteConnectionNatives.nativeExecute(Native Method)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.lambda$nativeExecute$10(ShadowNativeSQLiteConnection.java:132)
	at org.robolectric.util.PerfStatsCollector.measure(PerfStatsCollector.java:83)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.nativeExecute(ShadowNativeSQLiteConnection.java:130)
	at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
	at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:730)
	at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:621)
	at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:47)
	at androidx.sqlite.db.framework.FrameworkSQLiteStatement.execute(FrameworkSQLiteStatement.android.kt:30)
	at androidx.sqlite.driver.SupportSQLiteStatement$OtherSQLiteStatement.step(SupportSQLiteStatement.android.kt:540)
	at androidx.sqlite.SQLite__SQLite_nonWebKt.execSQL(SQLite.nonWeb.kt:26)
	at androidx.sqlite.SQLite.execSQL(Unknown Source)
	at org.meshtastic.core.database.MeshtasticDatabase_Impl$createOpenDelegate$_openDelegate$1.createAllTables(MeshtasticDatabase_Impl.kt:96)
	at androidx.room3.BaseRoomConnectionManager.onCreate(RoomConnectionManager.kt:190)
	at androidx.room3.BaseRoomConnectionManager.configureDatabase(RoomConnectionManager.kt:130)
	at androidx.room3.BaseRoomConnectionManager.access$configureDatabase(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invokeSuspend(ExclusiveMutex.kt:97)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invoke(ExclusiveMutex.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.BaseRoomConnectionManager.openLocked(RoomConnectionManager.kt:367)
	at androidx.room3.BaseRoomConnectionManager.access$openLocked(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invokeSuspend(RoomConnectionManager.kt:64)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invoke(RoomConnectionManager.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invokeSuspend(PassthroughConnectionPool.kt:78)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invokeSuspend(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.PassthroughConnectionPool.withInitializationLock(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool.useConnection(PassthroughConnectionPool.kt:76)
	at androidx.room3.RoomConnectionManager.useConnection(RoomConnectionManager.android.kt:99)
	at androidx.room3.RoomDatabase.useConnection(RoomDatabase.android.kt:408)
	at androidx.room3.util.DBUtil__DBUtilKt$performSuspending$2.invokeSuspend(DBUtil.kt:208)
	at androidx.room3.util.DBUtil__DBUtilKt$performSuspending$2.invoke(DBUtil.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.util.DBUtil__DBUtilKt.performSuspending(DBUtil.kt:47)
	at androidx.room3.util.DBUtil.performSuspending(Unknown Source)
	at org.meshtastic.core.database.dao.PacketDao_Impl.backfillMessageTexts(PacketDao_Impl.kt:3027)
	at org.meshtastic.core.database.DatabaseManager.backfillSearchIndexIfNeeded(DatabaseManager.kt:317)
	at org.meshtastic.core.database.DatabaseManager.access$backfillSearchIndexIfNeeded(DatabaseManager.kt:49)
	at org.meshtastic.core.database.DatabaseManager$switchActiveDatabase$2$4.invokeSuspend(DatabaseManager.kt:153)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:798)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
View the full list of 4 ❄️ flaky test(s)
org.meshtastic.core.database.dao.MigrationTest::testMigrateChannelsByPSK_disambiguateByName

Flake rate in main: 13.33% (Passed 26 times, Failed 4 times)

Stack Traces | 19.2s run time
android.database.sqlite.SQLiteException: no such module: FTS5 (code 1 SQLITE_ERROR)
	at org.robolectric.nativeruntime.SQLiteConnectionNatives.nativeExecute(Native Method)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.lambda$nativeExecute$10(ShadowNativeSQLiteConnection.java:132)
	at org.robolectric.util.PerfStatsCollector.measure(PerfStatsCollector.java:83)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.nativeExecute(ShadowNativeSQLiteConnection.java:130)
	at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
	at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:730)
	at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:621)
	at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:47)
	at androidx.sqlite.db.framework.FrameworkSQLiteStatement.execute(FrameworkSQLiteStatement.android.kt:30)
	at androidx.sqlite.driver.SupportSQLiteStatement$OtherSQLiteStatement.step(SupportSQLiteStatement.android.kt:540)
	at androidx.sqlite.SQLite__SQLite_nonWebKt.execSQL(SQLite.nonWeb.kt:26)
	at androidx.sqlite.SQLite.execSQL(Unknown Source)
	at org.meshtastic.core.database.MeshtasticDatabase_Impl$createOpenDelegate$_openDelegate$1.createAllTables(MeshtasticDatabase_Impl.kt:96)
	at androidx.room3.BaseRoomConnectionManager.onCreate(RoomConnectionManager.kt:190)
	at androidx.room3.BaseRoomConnectionManager.configureDatabase(RoomConnectionManager.kt:130)
	at androidx.room3.BaseRoomConnectionManager.access$configureDatabase(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invokeSuspend(ExclusiveMutex.kt:97)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invoke(ExclusiveMutex.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.BaseRoomConnectionManager.openLocked(RoomConnectionManager.kt:367)
	at androidx.room3.BaseRoomConnectionManager.access$openLocked(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invokeSuspend(RoomConnectionManager.kt:64)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invoke(RoomConnectionManager.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invokeSuspend(PassthroughConnectionPool.kt:78)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invokeSuspend(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.PassthroughConnectionPool.withInitializationLock(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool.useConnection(PassthroughConnectionPool.kt:76)
	at androidx.room3.RoomConnectionManager.useConnection(RoomConnectionManager.android.kt:99)
	at androidx.room3.RoomDatabase.useConnection(RoomDatabase.android.kt:408)
	at androidx.room3.util.DBUtil__DBUtilKt$performSuspending$2.invokeSuspend(DBUtil.kt:208)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:798)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
org.meshtastic.core.database.dao.MigrationTest::testMigrateChannelsByPSK_duplicatePSK

Flake rate in main: 13.33% (Passed 26 times, Failed 4 times)

Stack Traces | 0.18s run time
android.database.sqlite.SQLiteException: no such module: FTS5 (code 1 SQLITE_ERROR)
	at org.robolectric.nativeruntime.SQLiteConnectionNatives.nativeExecute(Native Method)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.lambda$nativeExecute$10(ShadowNativeSQLiteConnection.java:132)
	at org.robolectric.util.PerfStatsCollector.measure(PerfStatsCollector.java:83)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.nativeExecute(ShadowNativeSQLiteConnection.java:130)
	at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
	at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:730)
	at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:621)
	at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:47)
	at androidx.sqlite.db.framework.FrameworkSQLiteStatement.execute(FrameworkSQLiteStatement.android.kt:30)
	at androidx.sqlite.driver.SupportSQLiteStatement$OtherSQLiteStatement.step(SupportSQLiteStatement.android.kt:540)
	at androidx.sqlite.SQLite__SQLite_nonWebKt.execSQL(SQLite.nonWeb.kt:26)
	at androidx.sqlite.SQLite.execSQL(Unknown Source)
	at org.meshtastic.core.database.MeshtasticDatabase_Impl$createOpenDelegate$_openDelegate$1.createAllTables(MeshtasticDatabase_Impl.kt:96)
	at androidx.room3.BaseRoomConnectionManager.onCreate(RoomConnectionManager.kt:190)
	at androidx.room3.BaseRoomConnectionManager.configureDatabase(RoomConnectionManager.kt:130)
	at androidx.room3.BaseRoomConnectionManager.access$configureDatabase(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invokeSuspend(ExclusiveMutex.kt:97)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invoke(ExclusiveMutex.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.BaseRoomConnectionManager.openLocked(RoomConnectionManager.kt:367)
	at androidx.room3.BaseRoomConnectionManager.access$openLocked(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invokeSuspend(RoomConnectionManager.kt:64)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invoke(RoomConnectionManager.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invokeSuspend(PassthroughConnectionPool.kt:78)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invokeSuspend(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.PassthroughConnectionPool.withInitializationLock(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool.useConnection(PassthroughConnectionPool.kt:76)
	at androidx.room3.RoomConnectionManager.useConnection(RoomConnectionManager.android.kt:99)
	at androidx.room3.RoomDatabase.useConnection(RoomDatabase.android.kt:408)
	at androidx.room3.util.DBUtil__DBUtilKt$performSuspending$2.invokeSuspend(DBUtil.kt:208)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:798)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
org.meshtastic.core.database.dao.MigrationTest::testMigrateChannelsByPSK_preferSameIndexIfStillAmbiguous

Flake rate in main: 13.33% (Passed 26 times, Failed 4 times)

Stack Traces | 0.172s run time
android.database.sqlite.SQLiteException: no such module: FTS5 (code 1 SQLITE_ERROR)
	at org.robolectric.nativeruntime.SQLiteConnectionNatives.nativeExecute(Native Method)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.lambda$nativeExecute$10(ShadowNativeSQLiteConnection.java:132)
	at org.robolectric.util.PerfStatsCollector.measure(PerfStatsCollector.java:83)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.nativeExecute(ShadowNativeSQLiteConnection.java:130)
	at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
	at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:730)
	at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:621)
	at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:47)
	at androidx.sqlite.db.framework.FrameworkSQLiteStatement.execute(FrameworkSQLiteStatement.android.kt:30)
	at androidx.sqlite.driver.SupportSQLiteStatement$OtherSQLiteStatement.step(SupportSQLiteStatement.android.kt:540)
	at androidx.sqlite.SQLite__SQLite_nonWebKt.execSQL(SQLite.nonWeb.kt:26)
	at androidx.sqlite.SQLite.execSQL(Unknown Source)
	at org.meshtastic.core.database.MeshtasticDatabase_Impl$createOpenDelegate$_openDelegate$1.createAllTables(MeshtasticDatabase_Impl.kt:96)
	at androidx.room3.BaseRoomConnectionManager.onCreate(RoomConnectionManager.kt:190)
	at androidx.room3.BaseRoomConnectionManager.configureDatabase(RoomConnectionManager.kt:130)
	at androidx.room3.BaseRoomConnectionManager.access$configureDatabase(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invokeSuspend(ExclusiveMutex.kt:97)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invoke(ExclusiveMutex.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.BaseRoomConnectionManager.openLocked(RoomConnectionManager.kt:367)
	at androidx.room3.BaseRoomConnectionManager.access$openLocked(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invokeSuspend(RoomConnectionManager.kt:64)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invoke(RoomConnectionManager.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invokeSuspend(PassthroughConnectionPool.kt:78)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invokeSuspend(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.PassthroughConnectionPool.withInitializationLock(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool.useConnection(PassthroughConnectionPool.kt:76)
	at androidx.room3.RoomConnectionManager.useConnection(RoomConnectionManager.android.kt:99)
	at androidx.room3.RoomDatabase.useConnection(RoomDatabase.android.kt:408)
	at androidx.room3.util.DBUtil__DBUtilKt$performSuspending$2.invokeSuspend(DBUtil.kt:208)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:798)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)
org.meshtastic.core.database.dao.MigrationTest::testMigrateChannelsByPSK_reorder

Flake rate in main: 13.33% (Passed 26 times, Failed 4 times)

Stack Traces | 0.187s run time
android.database.sqlite.SQLiteException: no such module: FTS5 (code 1 SQLITE_ERROR)
	at org.robolectric.nativeruntime.SQLiteConnectionNatives.nativeExecute(Native Method)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.lambda$nativeExecute$10(ShadowNativeSQLiteConnection.java:132)
	at org.robolectric.util.PerfStatsCollector.measure(PerfStatsCollector.java:83)
	at org.robolectric.shadows.ShadowNativeSQLiteConnection.nativeExecute(ShadowNativeSQLiteConnection.java:130)
	at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
	at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:730)
	at android.database.sqlite.SQLiteSession.execute(SQLiteSession.java:621)
	at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:47)
	at androidx.sqlite.db.framework.FrameworkSQLiteStatement.execute(FrameworkSQLiteStatement.android.kt:30)
	at androidx.sqlite.driver.SupportSQLiteStatement$OtherSQLiteStatement.step(SupportSQLiteStatement.android.kt:540)
	at androidx.sqlite.SQLite__SQLite_nonWebKt.execSQL(SQLite.nonWeb.kt:26)
	at androidx.sqlite.SQLite.execSQL(Unknown Source)
	at org.meshtastic.core.database.MeshtasticDatabase_Impl$createOpenDelegate$_openDelegate$1.createAllTables(MeshtasticDatabase_Impl.kt:96)
	at androidx.room3.BaseRoomConnectionManager.onCreate(RoomConnectionManager.kt:190)
	at androidx.room3.BaseRoomConnectionManager.configureDatabase(RoomConnectionManager.kt:130)
	at androidx.room3.BaseRoomConnectionManager.access$configureDatabase(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invokeSuspend(ExclusiveMutex.kt:97)
	at androidx.room3.BaseRoomConnectionManager$openLocked$$inlined$withLock$1.invoke(ExclusiveMutex.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.BaseRoomConnectionManager.openLocked(RoomConnectionManager.kt:367)
	at androidx.room3.BaseRoomConnectionManager.access$openLocked(RoomConnectionManager.kt:38)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invokeSuspend(RoomConnectionManager.kt:64)
	at androidx.room3.BaseRoomConnectionManager$createConnectionFactory$1.invoke(RoomConnectionManager.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invokeSuspend(PassthroughConnectionPool.kt:78)
	at androidx.room3.coroutines.PassthroughConnectionPool$useConnection$2.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invokeSuspend(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool$withInitializationLock$3.invoke(PassthroughConnectionPool.kt)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invokeSuspend(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.ReentrantMutexKt$withReentrantLock$2.invoke(ReentrantMutex.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatched(Undispatched.kt:66)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:43)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:497)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at androidx.room3.coroutines.ReentrantMutexKt.withReentrantLock(ReentrantMutex.kt:38)
	at androidx.room3.coroutines.PassthroughConnectionPool.withInitializationLock(PassthroughConnectionPool.kt:121)
	at androidx.room3.coroutines.PassthroughConnectionPool.useConnection(PassthroughConnectionPool.kt:76)
	at androidx.room3.RoomConnectionManager.useConnection(RoomConnectionManager.android.kt:99)
	at androidx.room3.RoomDatabase.useConnection(RoomDatabase.android.kt:408)
	at androidx.room3.util.DBUtil__DBUtilKt$performSuspending$2.invokeSuspend(DBUtil.kt:208)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:798)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@jamesarich jamesarich added this to the 2.8.0 milestone May 6, 2026
Adds full-text search to the messaging screen using SQLite FTS5:

- New PacketFts entity (content-sync FTS5 table) tracking message_text
- Search UI with contextual search bar, prev/next navigation, and result count
- Term highlighting in message bubbles during active search
- Scroll-to-match when navigating between search results
- Efficient backfill via json_extract SQL UPDATE (no OOM risk)
- Query sanitization wrapping tokens in quotes to escape FTS5 special chars
- 300ms debounce with minimum 2-char threshold

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jamesarich jamesarich force-pushed the feat/fts5-message-search branch from 0e59ed7 to b117e19 Compare May 28, 2026 20:01
@github-actions
Copy link
Copy Markdown
Contributor

📄 Docs staleness check — advisory

This PR modifies user-facing UI source files but does not update any page under docs/en/user/ or docs/en/developer/.

⚠️ Doc changes propagate to 3 consumers: in-app docs browser, Jekyll site (GitHub Pages), and meshtastic.org (Docusaurus sync). Updating a page in docs/en/ automatically flows to all three.

Changed source files:

core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/AutoLinkText.kt
feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/ConnectionsScreen.kt
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageItem.kt
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageScreenComponents.kt

What to check:

Changed area Likely doc page
feature/messaging/ docs/en/user/messages-and-channels.md
feature/node/ docs/en/user/nodes.md or docs/en/user/node-metrics.md
feature/map/ docs/en/user/map-and-waypoints.md
feature/connections/ docs/en/user/connections.md
feature/settings/ docs/en/user/settings-radio-user.md or docs/en/user/settings-module-admin.md
feature/firmware/ docs/en/user/firmware.md
feature/intro/ docs/en/user/onboarding.md
feature/discovery/ docs/en/user/discovery.md
feature/docs/ Internal docs infrastructure
core/ui/ docs/en/developer/codebase.md or component-specific user pages

New page checklist (if adding a new doc page):

  1. Create the .md file in docs/en/user/ or docs/en/developer/ with last_updated frontmatter
  2. Register in DocBundleLoader.kt with string resources (in-app browser)
  3. Jekyll and Docusaurus sync pick up new pages automatically — no config change needed

If this PR does not require a doc update (e.g., internal refactor, bug fix, test change), add the skip-docs-check label to dismiss this check.

Cross-platform note: This check is advisory while doc coverage matures. Both Android and Apple repos use the same skip-docs-check label and advisory severity. See meshtastic/design standards for shared conventions.

@github-actions
Copy link
Copy Markdown
Contributor

🖼️ Preview staleness check — advisory

This PR modifies UI composables but does not update any *Previews.kt files.

Previews power screenshot tests and in-app docs screenshots. Keeping them current ensures visual regression coverage stays accurate.

Changed UI files:

feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/ui/ConnectionsScreen.kt
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageItem.kt
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageScreenComponents.kt

What to check:

Pattern Preview file convention
feature/{name}/…/ui/ or component/ feature/{name}/…/*Previews.kt
core/ui/…/ core/ui/…/ (previews colocated)

Adding previews checklist:

  1. Create or update a *Previews.kt file in the same module with @PreviewLightDark
  2. Add @Suppress("PreviewPublic") if the preview is consumed by screenshot-tests
  3. Add corresponding @PreviewTest function in screenshot-tests/src/screenshotTest/
  4. Run ./gradlew :screenshot-tests:updateDebugScreenshotTest to generate reference images

If this PR does not require preview updates (e.g., logic-only change, non-visual refactor), add the skip-preview-check label to dismiss.

jamesarich added a commit that referenced this pull request May 30, 2026
Squash merge of PR #5373 into release/2.8.0.

Adds full-text search to the messaging screen using SQLite FTS5, enabling
users to find messages within a conversation. Includes PacketFts entity,
FTS5 MATCH queries, backfill migration, and search UI with real-time results.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant